mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-01-31 00:55:19 +01:00
Dirents backport (#3876)
* lseek for directories behaves correctly when final index is smaller than 0 (EINVAL) Backported and improved dirents from QFS Normal directory dirents update on change * PFS moves pointer to end when last dirent is returned * Correct entry type in PFS directory
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
#include "common/singleton.h"
|
||||
#include "core/file_sys/directories/base_directory.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/kernel/orbis_error.h"
|
||||
|
||||
namespace Core::Directories {
|
||||
|
||||
@@ -12,4 +13,35 @@ BaseDirectory::BaseDirectory() = default;
|
||||
|
||||
BaseDirectory::~BaseDirectory() = default;
|
||||
|
||||
s64 BaseDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
s64 bytes_read = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
bytes_read += result;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 BaseDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 BaseDirectory::lseek(s64 offset, s32 whence) {
|
||||
|
||||
s64 file_offset_new = ((0 == whence) * offset) + ((1 == whence) * (file_offset + offset)) +
|
||||
((2 == whence) * (directory_size + offset));
|
||||
if (file_offset_new < 0)
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
|
||||
file_offset = file_offset_new;
|
||||
return file_offset;
|
||||
}
|
||||
|
||||
} // namespace Core::Directories
|
||||
@@ -19,6 +19,17 @@ struct OrbisKernelDirent;
|
||||
namespace Core::Directories {
|
||||
|
||||
class BaseDirectory {
|
||||
protected:
|
||||
static inline u32 fileno_pool{10};
|
||||
|
||||
static u32 next_fileno() {
|
||||
return ++fileno_pool;
|
||||
}
|
||||
|
||||
s64 file_offset = 0;
|
||||
u64 directory_size = 0;
|
||||
std::vector<u8> dirent_cache_bin{};
|
||||
|
||||
public:
|
||||
explicit BaseDirectory();
|
||||
|
||||
@@ -28,13 +39,8 @@ public:
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt);
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset);
|
||||
|
||||
virtual s64 write(const void* buf, u64 nbytes) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
@@ -48,9 +54,7 @@ public:
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
|
||||
virtual s64 lseek(s64 offset, s32 whence) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
}
|
||||
virtual s64 lseek(s64 offset, s32 whence);
|
||||
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
return ORBIS_KERNEL_ERROR_EBADF;
|
||||
|
||||
@@ -15,111 +15,30 @@ std::shared_ptr<BaseDirectory> NormalDirectory::Create(std::string_view guest_di
|
||||
std::make_shared<NormalDirectory>(guest_directory));
|
||||
}
|
||||
|
||||
NormalDirectory::NormalDirectory(std::string_view guest_directory) {
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static s32 fileno = 0;
|
||||
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dirent = dirents.emplace_back();
|
||||
dirent.d_fileno = ++fileno;
|
||||
dirent.d_type = (ent_is_file ? 8 : 4);
|
||||
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
|
||||
dirent.d_namlen = ent_path.filename().string().size();
|
||||
|
||||
// Calculate the appropriate length for this dirent.
|
||||
// Account for the null terminator in d_name too.
|
||||
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
|
||||
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
|
||||
(dirent.d_namlen + 1),
|
||||
4);
|
||||
|
||||
directory_size += dirent.d_reclen;
|
||||
});
|
||||
|
||||
// The last entry of a normal directory should have d_reclen covering the remaining data.
|
||||
// Since the dirents of a folder are constant by this point, we can modify the last dirent
|
||||
// before creating the emulated file buffer.
|
||||
const u64 filler_count = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT) - directory_size;
|
||||
dirents[dirents.size() - 1].d_reclen += filler_count;
|
||||
|
||||
// Reading from standard directories seems to be based around file pointer logic.
|
||||
// Keep an internal buffer representing the raw contents of this file descriptor,
|
||||
// then emulate the various read functions with that.
|
||||
directory_size = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT);
|
||||
data_buffer.reserve(directory_size);
|
||||
memset(data_buffer.data(), 0, directory_size);
|
||||
|
||||
u8* current_dirent = data_buffer.data();
|
||||
for (const NormalDirectoryDirent& dirent : dirents) {
|
||||
NormalDirectoryDirent* dirent_to_write =
|
||||
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
|
||||
// Using size d_namlen + 1 to account for null terminator.
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
}
|
||||
NormalDirectory::NormalDirectory(std::string_view guest_directory)
|
||||
: guest_directory(guest_directory) {
|
||||
RebuildDirents();
|
||||
}
|
||||
|
||||
s64 NormalDirectory::read(void* buf, u64 nbytes) {
|
||||
// Nothing left to read.
|
||||
if (file_offset >= directory_size) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
RebuildDirents();
|
||||
|
||||
const s64 remaining_data = directory_size - file_offset;
|
||||
const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes;
|
||||
// data is contiguous. read goes like any regular file would: start at offset, read n bytes
|
||||
// output is always aligned up to 512 bytes with 0s
|
||||
// offset - classic. however at the end of read any unused (exceeding dirent buffer size) buffer
|
||||
// space will be left untouched
|
||||
// reclen always sums up to end of current alignment
|
||||
|
||||
std::memcpy(buf, data_buffer.data() + file_offset, bytes);
|
||||
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
|
||||
if (bytes_available <= 0)
|
||||
return 0;
|
||||
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
|
||||
|
||||
file_offset += bytes;
|
||||
return bytes;
|
||||
}
|
||||
// data
|
||||
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
|
||||
|
||||
s64 NormalDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
s64 bytes_read = 0;
|
||||
for (s32 i = 0; i < iovcnt; i++) {
|
||||
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
|
||||
if (result < 0) {
|
||||
return result;
|
||||
}
|
||||
bytes_read += result;
|
||||
}
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 NormalDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) {
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 NormalDirectory::lseek(s64 offset, s32 whence) {
|
||||
switch (whence) {
|
||||
case 0: {
|
||||
file_offset = offset;
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
file_offset += offset;
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
file_offset = directory_size + offset;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
|
||||
}
|
||||
}
|
||||
return file_offset;
|
||||
file_offset += bytes_available;
|
||||
return bytes_available;
|
||||
}
|
||||
|
||||
s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
@@ -131,10 +50,110 @@ s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
}
|
||||
|
||||
s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
|
||||
if (basep != nullptr) {
|
||||
RebuildDirents();
|
||||
|
||||
if (basep)
|
||||
*basep = file_offset;
|
||||
|
||||
// same as others, we just don't need a variable
|
||||
if (file_offset >= directory_size)
|
||||
return 0;
|
||||
|
||||
s64 bytes_written = 0;
|
||||
s64 working_offset = file_offset;
|
||||
s64 dirent_buffer_offset = 0;
|
||||
s64 aligned_count = Common::AlignDown(nbytes, 512);
|
||||
|
||||
const u8* dirent_buffer = this->dirent_cache_bin.data();
|
||||
while (dirent_buffer_offset < this->dirent_cache_bin.size()) {
|
||||
const u8* normal_dirent_ptr = dirent_buffer + dirent_buffer_offset;
|
||||
const NormalDirectoryDirent* normal_dirent =
|
||||
reinterpret_cast<const NormalDirectoryDirent*>(normal_dirent_ptr);
|
||||
auto d_reclen = normal_dirent->d_reclen;
|
||||
|
||||
// bad, incomplete or OOB entry
|
||||
if (normal_dirent->d_namlen == 0)
|
||||
break;
|
||||
|
||||
if (working_offset >= d_reclen) {
|
||||
dirent_buffer_offset += d_reclen;
|
||||
working_offset -= d_reclen;
|
||||
continue;
|
||||
}
|
||||
// read behaves identically to getdents for normal directories.
|
||||
return read(buf, nbytes);
|
||||
|
||||
if ((bytes_written + d_reclen) > aligned_count)
|
||||
// dirents are aligned to the last full one
|
||||
break;
|
||||
|
||||
memcpy(static_cast<u8*>(buf) + bytes_written, normal_dirent_ptr + working_offset,
|
||||
d_reclen - working_offset);
|
||||
bytes_written += d_reclen - working_offset;
|
||||
dirent_buffer_offset += d_reclen;
|
||||
working_offset = 0;
|
||||
}
|
||||
|
||||
file_offset += bytes_written;
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
void NormalDirectory::RebuildDirents() {
|
||||
// regenerate only when target wants to read contents again
|
||||
// no reason for testing - read is always raw and dirents get processed on the go
|
||||
if (previous_file_offset == file_offset)
|
||||
return;
|
||||
previous_file_offset = file_offset;
|
||||
|
||||
constexpr u32 dirent_meta_size =
|
||||
sizeof(NormalDirectoryDirent::d_fileno) + sizeof(NormalDirectoryDirent::d_type) +
|
||||
sizeof(NormalDirectoryDirent::d_namlen) + sizeof(NormalDirectoryDirent::d_reclen);
|
||||
|
||||
u64 next_ceiling = 0;
|
||||
u64 dirent_offset = 0;
|
||||
u64 last_reclen_offset = 4;
|
||||
dirent_cache_bin.clear();
|
||||
dirent_cache_bin.reserve(512);
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
mnt->IterateDirectory(
|
||||
guest_directory, [this, &next_ceiling, &dirent_offset, &last_reclen_offset](
|
||||
const std::filesystem::path& ent_path, const bool ent_is_file) {
|
||||
NormalDirectoryDirent tmp{};
|
||||
std::string leaf(ent_path.filename().string());
|
||||
|
||||
// prepare dirent
|
||||
tmp.d_fileno = BaseDirectory::next_fileno();
|
||||
tmp.d_namlen = leaf.size();
|
||||
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
|
||||
tmp.d_type = (ent_is_file ? 0100000 : 0040000) >> 12;
|
||||
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 4);
|
||||
|
||||
// next element may break 512 byte alignment
|
||||
if (tmp.d_reclen + dirent_offset > next_ceiling) {
|
||||
// align previous dirent's size to the current ceiling
|
||||
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) +
|
||||
last_reclen_offset) += next_ceiling - dirent_offset;
|
||||
// set writing pointer to the aligned start position (current ceiling)
|
||||
dirent_offset = next_ceiling;
|
||||
// move the ceiling up and zero-out the buffer
|
||||
next_ceiling += 512;
|
||||
dirent_cache_bin.resize(next_ceiling);
|
||||
std::fill(dirent_cache_bin.begin() + dirent_offset,
|
||||
dirent_cache_bin.begin() + next_ceiling, 0);
|
||||
}
|
||||
|
||||
// current dirent's reclen position
|
||||
last_reclen_offset = dirent_offset + 4;
|
||||
memcpy(dirent_cache_bin.data() + dirent_offset, &tmp, tmp.d_reclen);
|
||||
dirent_offset += tmp.d_reclen;
|
||||
});
|
||||
|
||||
// last reclen, as before
|
||||
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) + last_reclen_offset) +=
|
||||
next_ceiling - dirent_offset;
|
||||
|
||||
// i have no idea if this is the case, but lseek returns size aligned to 512
|
||||
directory_size = next_ceiling;
|
||||
}
|
||||
|
||||
} // namespace Core::Directories
|
||||
@@ -19,27 +19,23 @@ public:
|
||||
~NormalDirectory() override = default;
|
||||
|
||||
virtual s64 read(void* buf, u64 nbytes) override;
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) override;
|
||||
virtual s64 lseek(s64 offset, s32 whence) override;
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
|
||||
|
||||
private:
|
||||
static constexpr s32 MAX_LENGTH = 255;
|
||||
static constexpr s64 DIRECTORY_ALIGNMENT = 0x200;
|
||||
#pragma pack(push, 1)
|
||||
struct NormalDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u16 d_reclen;
|
||||
u8 d_type;
|
||||
u8 d_namlen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
u64 directory_size = 0;
|
||||
s64 file_offset = 0;
|
||||
std::vector<u8> data_buffer;
|
||||
std::vector<NormalDirectoryDirent> dirents;
|
||||
std::string_view guest_directory{};
|
||||
s64 previous_file_offset = -1;
|
||||
|
||||
void RebuildDirents(void);
|
||||
};
|
||||
} // namespace Core::Directories
|
||||
|
||||
@@ -15,77 +15,49 @@ std::shared_ptr<BaseDirectory> PfsDirectory::Create(std::string_view guest_direc
|
||||
}
|
||||
|
||||
PfsDirectory::PfsDirectory(std::string_view guest_directory) {
|
||||
constexpr u32 dirent_meta_size =
|
||||
sizeof(PfsDirectoryDirent::d_fileno) + sizeof(PfsDirectoryDirent::d_type) +
|
||||
sizeof(PfsDirectoryDirent::d_namlen) + sizeof(PfsDirectoryDirent::d_reclen);
|
||||
|
||||
dirent_cache_bin.reserve(512);
|
||||
|
||||
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
|
||||
|
||||
static s32 fileno = 0;
|
||||
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
|
||||
auto& dirent = dirents.emplace_back();
|
||||
dirent.d_fileno = ++fileno;
|
||||
dirent.d_type = (ent_is_file ? 8 : 4);
|
||||
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
|
||||
dirent.d_namlen = ent_path.filename().string().size();
|
||||
mnt->IterateDirectory(
|
||||
guest_directory, [this](const std::filesystem::path& ent_path, const bool ent_is_file) {
|
||||
PfsDirectoryDirent tmp{};
|
||||
std::string leaf(ent_path.filename().string());
|
||||
|
||||
// Calculate the appropriate length for this dirent.
|
||||
// Account for the null terminator in d_name too.
|
||||
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
|
||||
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
|
||||
(dirent.d_namlen + 1),
|
||||
8);
|
||||
tmp.d_fileno = BaseDirectory::next_fileno();
|
||||
tmp.d_namlen = leaf.size();
|
||||
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
|
||||
tmp.d_type = ent_is_file ? 2 : 4;
|
||||
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 8);
|
||||
auto dirent_ptr = reinterpret_cast<const u8*>(&tmp);
|
||||
|
||||
// To handle some obscure dirents_index behavior,
|
||||
// keep track of the "actual" length of this directory.
|
||||
directory_content_size += dirent.d_reclen;
|
||||
dirent_cache_bin.insert(dirent_cache_bin.end(), dirent_ptr, dirent_ptr + tmp.d_reclen);
|
||||
});
|
||||
|
||||
directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT);
|
||||
directory_size = Common::AlignUp(dirent_cache_bin.size(), 0x10000);
|
||||
}
|
||||
|
||||
s64 PfsDirectory::read(void* buf, u64 nbytes) {
|
||||
if (dirents_index >= dirents.size()) {
|
||||
if (dirents_index < directory_content_size) {
|
||||
// We need to find the appropriate dirents_index to start from.
|
||||
s64 data_to_skip = dirents_index;
|
||||
u64 corrected_index = 0;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[corrected_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
|
||||
if (bytes_available <= 0)
|
||||
return 0;
|
||||
|
||||
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
|
||||
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
|
||||
|
||||
s64 to_fill =
|
||||
(std::min<s64>(directory_size, static_cast<s64>(nbytes))) - bytes_available - file_offset;
|
||||
if (to_fill < 0) {
|
||||
LOG_ERROR(Kernel_Fs, "Dirent may have leaked {} bytes", -to_fill);
|
||||
return -to_fill + bytes_available;
|
||||
}
|
||||
dirents_index = corrected_index;
|
||||
} else {
|
||||
// Nothing left to read.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
|
||||
// read on PfsDirectories will always return the maximum possible value.
|
||||
const u64 bytes_written = bytes_remaining;
|
||||
memset(buf, 0, bytes_remaining);
|
||||
|
||||
char* current_dirent = static_cast<char*>(buf);
|
||||
PfsDirectoryDirent dirent = dirents[dirents_index];
|
||||
while (bytes_remaining > dirent.d_reclen) {
|
||||
PfsDirectoryDirent* dirent_to_write = reinterpret_cast<PfsDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
|
||||
// Using size d_namlen + 1 to account for null terminator.
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
bytes_remaining -= dirent.d_reclen;
|
||||
|
||||
if (dirents_index == dirents.size() - 1) {
|
||||
// Currently at the last dirent, so break out of the loop.
|
||||
dirents_index++;
|
||||
break;
|
||||
}
|
||||
dirent = dirents[++dirents_index];
|
||||
}
|
||||
|
||||
return bytes_written;
|
||||
memset(static_cast<u8*>(buf) + bytes_available, 0, to_fill);
|
||||
file_offset += to_fill + bytes_available;
|
||||
return to_fill + bytes_available;
|
||||
}
|
||||
|
||||
s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
|
||||
@@ -101,62 +73,13 @@ s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovc
|
||||
}
|
||||
|
||||
s64 PfsDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
|
||||
const u64 old_dirent_index = dirents_index;
|
||||
dirents_index = 0;
|
||||
s64 data_to_skip = offset;
|
||||
// If offset is part-way through one dirent, that dirent is skipped.
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[dirents_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
if (dirents_index == dirents.size()) {
|
||||
// We've reached the end of the dirents, nothing more can be skipped.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const u64 old_file_pointer = file_offset;
|
||||
file_offset = offset;
|
||||
const s64 bytes_read = readv(iov, iovcnt);
|
||||
dirents_index = old_dirent_index;
|
||||
file_offset = old_file_pointer;
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
s64 PfsDirectory::lseek(s64 offset, s32 whence) {
|
||||
switch (whence) {
|
||||
// Seek start
|
||||
case 0: {
|
||||
dirents_index = 0;
|
||||
}
|
||||
case 1: {
|
||||
// There aren't any dirents left to pass through.
|
||||
if (dirents_index >= dirents.size()) {
|
||||
dirents_index = dirents_index + offset;
|
||||
break;
|
||||
}
|
||||
s64 data_to_skip = offset;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[dirents_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
if (dirents_index == dirents.size()) {
|
||||
// We've passed through all file dirents.
|
||||
// Set dirents_index to directory_size + remaining_offset instead.
|
||||
dirents_index = directory_content_size + data_to_skip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// Seems like real hardware gives up on tracking dirents_index if you go this route.
|
||||
dirents_index = directory_size + offset;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
|
||||
}
|
||||
}
|
||||
|
||||
return dirents_index;
|
||||
}
|
||||
|
||||
s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
stat->st_mode = 0000777u | 0040000u;
|
||||
stat->st_size = directory_size;
|
||||
@@ -166,55 +89,58 @@ s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
|
||||
}
|
||||
|
||||
s64 PfsDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
|
||||
// basep is set at the start of the function.
|
||||
if (basep != nullptr) {
|
||||
*basep = dirents_index;
|
||||
}
|
||||
if (basep)
|
||||
*basep = file_offset;
|
||||
|
||||
if (dirents_index >= dirents.size()) {
|
||||
if (dirents_index < directory_content_size) {
|
||||
// We need to find the appropriate dirents_index to start from.
|
||||
s64 data_to_skip = dirents_index;
|
||||
u64 corrected_index = 0;
|
||||
while (data_to_skip > 0) {
|
||||
const auto dirent = dirents[corrected_index++];
|
||||
data_to_skip -= dirent.d_reclen;
|
||||
}
|
||||
dirents_index = corrected_index;
|
||||
} else {
|
||||
// Nothing left to read.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
|
||||
memset(buf, 0, bytes_remaining);
|
||||
// same as others, we just don't need a variable
|
||||
if (file_offset >= directory_size)
|
||||
return 0;
|
||||
|
||||
u64 bytes_written = 0;
|
||||
char* current_dirent = static_cast<char*>(buf);
|
||||
// getdents has to convert pfs dirents to normal dirents
|
||||
PfsDirectoryDirent dirent = dirents[dirents_index];
|
||||
while (bytes_remaining > dirent.d_reclen) {
|
||||
NormalDirectoryDirent* dirent_to_write =
|
||||
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
|
||||
dirent_to_write->d_fileno = dirent.d_fileno;
|
||||
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
|
||||
dirent_to_write->d_namlen = dirent.d_namlen;
|
||||
dirent_to_write->d_reclen = dirent.d_reclen;
|
||||
dirent_to_write->d_type = dirent.d_type;
|
||||
u64 starting_offset = 0;
|
||||
u64 buffer_position = 0;
|
||||
while (buffer_position < this->dirent_cache_bin.size()) {
|
||||
const PfsDirectoryDirent* pfs_dirent =
|
||||
reinterpret_cast<PfsDirectoryDirent*>(this->dirent_cache_bin.data() + buffer_position);
|
||||
|
||||
current_dirent += dirent.d_reclen;
|
||||
bytes_remaining -= dirent.d_reclen;
|
||||
bytes_written += dirent.d_reclen;
|
||||
|
||||
if (dirents_index == dirents.size() - 1) {
|
||||
// Currently at the last dirent, so set dirents_index appropriately and break.
|
||||
dirents_index = directory_size;
|
||||
// bad, incomplete or OOB entry
|
||||
if (pfs_dirent->d_namlen == 0)
|
||||
break;
|
||||
}
|
||||
dirent = dirents[++dirents_index];
|
||||
|
||||
if (starting_offset < file_offset) {
|
||||
// reading starts from the nearest full dirent
|
||||
starting_offset += pfs_dirent->d_reclen;
|
||||
buffer_position = bytes_written + starting_offset;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((bytes_written + pfs_dirent->d_reclen) > nbytes)
|
||||
// dirents are aligned to the last full one
|
||||
break;
|
||||
|
||||
// if this dirent breaks alignment, skip
|
||||
// dirents are count-aligned here, excess data is simply not written
|
||||
// if (Common::AlignUp(buffer_position, count) !=
|
||||
// Common::AlignUp(buffer_position + pfs_dirent->d_reclen, count))
|
||||
// break;
|
||||
|
||||
// reclen for both is the same despite difference in var sizes, extra 0s are padded after
|
||||
// the name
|
||||
NormalDirectoryDirent normal_dirent{};
|
||||
normal_dirent.d_fileno = pfs_dirent->d_fileno;
|
||||
normal_dirent.d_reclen = pfs_dirent->d_reclen;
|
||||
normal_dirent.d_type = (pfs_dirent->d_type == 2) ? 8 : 4;
|
||||
normal_dirent.d_namlen = pfs_dirent->d_namlen;
|
||||
memcpy(normal_dirent.d_name, pfs_dirent->d_name, pfs_dirent->d_namlen);
|
||||
|
||||
memcpy(static_cast<u8*>(buf) + bytes_written, &normal_dirent, normal_dirent.d_reclen);
|
||||
bytes_written += normal_dirent.d_reclen;
|
||||
buffer_position = bytes_written + starting_offset;
|
||||
}
|
||||
|
||||
file_offset = (buffer_position >= this->dirent_cache_bin.size())
|
||||
? directory_size
|
||||
: (file_offset + bytes_written);
|
||||
return bytes_written;
|
||||
}
|
||||
} // namespace Core::Directories
|
||||
@@ -22,32 +22,28 @@ public:
|
||||
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
|
||||
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
|
||||
s64 offset) override;
|
||||
virtual s64 lseek(s64 offset, s32 whence) override;
|
||||
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
|
||||
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
|
||||
|
||||
private:
|
||||
static constexpr s32 MAX_LENGTH = 255;
|
||||
static constexpr s32 DIRECTORY_ALIGNMENT = 0x10000;
|
||||
#pragma pack(push, 1)
|
||||
struct PfsDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u32 d_type;
|
||||
u32 d_namlen;
|
||||
u32 d_reclen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct NormalDirectoryDirent {
|
||||
u32 d_fileno;
|
||||
u16 d_reclen;
|
||||
u8 d_type;
|
||||
u8 d_namlen;
|
||||
char d_name[MAX_LENGTH + 1];
|
||||
char d_name[256];
|
||||
};
|
||||
|
||||
u64 directory_size = 0;
|
||||
u64 directory_content_size = 0;
|
||||
s64 dirents_index = 0;
|
||||
std::vector<PfsDirectoryDirent> dirents;
|
||||
#pragma pack(pop)
|
||||
};
|
||||
} // namespace Core::Directories
|
||||
|
||||
Reference in New Issue
Block a user