diff --git a/src/core/file_sys/quasifs/quasifs_inode_quasi_directory.h b/src/core/file_sys/quasifs/quasifs_inode_quasi_directory.h index c7168820e..2ddf5d727 100644 --- a/src/core/file_sys/quasifs/quasifs_inode_quasi_directory.h +++ b/src/core/file_sys/quasifs/quasifs_inode_quasi_directory.h @@ -62,6 +62,7 @@ public: virtual s64 pread(void* buf, u64 count, s64 offset) override; // s64 pwrite(const void* buf, size_t count, u64 offset) override; + s64 lseek(s64 current, s64 offset, s32 whence) override; s32 fstat(Libraries::Kernel::OrbisKernelStat* sb) override; s32 ftruncate(s64 length) final override; diff --git a/src/core/file_sys/quasifs/quasifs_inode_quasi_directory_pfs.h b/src/core/file_sys/quasifs/quasifs_inode_quasi_directory_pfs.h index deb0498a5..c32f134c2 100644 --- a/src/core/file_sys/quasifs/quasifs_inode_quasi_directory_pfs.h +++ b/src/core/file_sys/quasifs/quasifs_inode_quasi_directory_pfs.h @@ -45,8 +45,8 @@ public: } s64 pread(void* buf, u64 count, s64 offset) override; + s64 lseek(s64 current, s64 offset, s32 whence) override; - // s64 lseek(s64 current, s64 offset, s32 whence) override; s64 getdents(void* buf, u32 count, s64 offset, s64* basep) override; }; diff --git a/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory.cpp b/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory.cpp index ba5f20110..c1ce2db0b 100644 --- a/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory.cpp +++ b/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory.cpp @@ -24,6 +24,11 @@ s64 QuasiDirectory::pread(void* buf, u64 count, s64 offset) { return getdents(buf, count, offset, nullptr); } +s64 QuasiDirectory::lseek(s64 current, s64 offset, s32 whence) { + RebuildDirents(); + return Inode::lseek(current, offset, whence); +} + s32 QuasiDirectory::fstat(Libraries::Kernel::OrbisKernelStat* sb) { RebuildDirents(); *sb = st; @@ -42,20 +47,15 @@ s64 QuasiDirectory::getdents(void* buf, u32 count, s64 offset, s64* basep) { // return always alignd final fptr to 512 bytes // doesn't zero-out remaining space in buffer + // we're assuming this is always aligned, no check here s64 bytes_available = this->dirent_cache_bin.size() - offset; if (bytes_available <= 0) return 0; - if (count > 512) - count = 512; - u64 apparent_end = count + offset; + // offset might push it too far so read count becomes misaligned + u64 apparent_end = offset + count; u64 minimum_read = Common::AlignDown(apparent_end, 512) - offset; - u64 maximum_read = Common::AlignUp(apparent_end, 512) - offset; - - u64 to_read = std::min(minimum_read, maximum_read); - - if (to_read > bytes_available) - UNREACHABLE_MSG("Buffer read/write misaligned from 512 bytes"); + u64 to_read = bytes_available > minimum_read ? minimum_read : bytes_available; std::copy(dirent_cache_bin.data() + offset, dirent_cache_bin.data() + offset + to_read, static_cast(buf)); diff --git a/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory_pfs.cpp b/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory_pfs.cpp index 5ef9e8ca2..03f2711b4 100644 --- a/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory_pfs.cpp +++ b/src/core/file_sys/quasifs/src/quasifs_inode_quasi_directory_pfs.cpp @@ -7,41 +7,65 @@ #include "core/file_sys/quasifs/quasi_errno.h" #include "core/file_sys/quasifs/quasifs_inode_quasi_directory_pfs.h" +// PFS is a bit different from regular dirents, see comments below +// Although it's pretty simple, every tested game (sample size: 1) reads it exclusively with +// count=65536, so it doesn't need much mumbo-jambo like regular dirents +// I'll worry if a game uses something different than that count and offset=0 + namespace QuasiFS { DirectoryPFS::DirectoryPFS() { - this->st.st_size = 65536; + this->st.st_size = 65536; // pro forma, gets erased on sync. TODO: fixme } DirectoryPFS::~DirectoryPFS() = default; s64 DirectoryPFS::pread(void* buf, u64 count, s64 offset) { RebuildDirents(); - memset(buf, 0, count); // data is contiguous. i think that's how it's said // anyway, everything goes raw - // always returns count - // always zeroes buffer - // no cut-off, will just spew out data whenever you ask for it + // aligned to 65536 bytes, everything is zeroed-out in that range + // currently not testing for anything higher than that - if (offset >= this->dirent_cache_bin.size()) - return count; + s64 apparent_end = offset + count; + + if (apparent_end > 65536) { + // i really don't want to do this yet + LOG_CRITICAL(Kernel_Fs, + "PFS directory size larger than 65536 bytes is not implemented yet"); + if (offset > 65536) + return 0; + count = 65536 - offset; + } s64 bytes_available = this->dirent_cache_bin.size() - offset; - // bytes_to_read but retains the same variable lmao - if (bytes_available >= count) - bytes_available = count; + if (0 >= bytes_available) + return 0; + bytes_available = bytes_available > count ? count : bytes_available; + // data memcpy(buf, this->dirent_cache_bin.data() + offset, bytes_available); + // remainders + s64 filler = count - bytes_available; + if (filler > 0) + memset(static_cast(buf) + bytes_available, 0, filler); // always returns count return count; } +s64 DirectoryPFS::lseek(s64 current, s64 offset, s32 whence) { + RebuildDirents(); + return Inode::lseek(current, offset, whence); +} + s64 DirectoryPFS::getdents(void* buf, u32 count, s64 offset, s64* basep) { RebuildDirents(); - memset(buf, 0, count); + + if (count != 65536) + LOG_CRITICAL(Kernel_Fs, "PFS dirents read with count={} (which is not 65536) (report this)", + count); // honestly i have no idea on how to implement this // buffer behaves like a regular directory @@ -50,7 +74,52 @@ s64 DirectoryPFS::getdents(void* buf, u32 count, s64 offset, s64* basep) { // for some reason, subsequent calls return 0 // doesn't zero-out memory after last dirent - return count; + s64 apparent_end = offset + count; + + if (apparent_end > 65536) { + // i really don't want to do this yet + LOG_CRITICAL(Kernel_Fs, + "PFS directory size larger than 65536 bytes is not implemented yet"); + if (offset > 65536) + return 0; + count = 65536 - offset; + } + + s64 bytes_available = this->dirent_cache_bin.size() - offset; + if (0 >= bytes_available) + return 0; + bytes_available = bytes_available > count ? count : bytes_available; + + u64 bytes_written = 0; + u64 dirent_offset = 0; + while (dirent_offset < bytes_available) { + const dirent_pfs_t* pfs_dirent = + reinterpret_cast(this->dirent_cache_bin.data() + bytes_written); + dirent_t normal_dirent{}; + + // we're transposing u32 into smaller types, so there miiight be some issues + normal_dirent.d_fileno = pfs_dirent->d_fileno; + normal_dirent.d_reclen = pfs_dirent->d_reclen; + normal_dirent.d_type = pfs_dirent->d_type; + normal_dirent.d_namlen = pfs_dirent->d_namlen; + memcpy(normal_dirent.d_name, pfs_dirent->d_name, pfs_dirent->d_namlen); + + s64 bytes_remaining = bytes_available - bytes_written; + u64 to_write = 0; + + if (bytes_remaining > normal_dirent.d_reclen) + to_write = normal_dirent.d_reclen; + else if (bytes_remaining > 0) { + to_write = bytes_remaining; + } + + memcpy(static_cast(buf) + bytes_written, &normal_dirent, to_write); + dirent_offset += pfs_dirent->d_reclen; + bytes_written += to_write; + } + + // always returns count + return bytes_written; } void DirectoryPFS::RebuildDirents(void) { @@ -84,6 +153,9 @@ void DirectoryPFS::RebuildDirents(void) { } // directory size is always 65536 bytes + // it gets erased on FS sync in QFS, but this fn is rarely called, + // especially for PFS which is RO + this->st.st_size = 65536; return; }