This commit is contained in:
Marek Ledworowski 2025-11-16 19:48:31 +01:00
parent 748d81b647
commit 98ec0d073e
9 changed files with 17 additions and 536 deletions

View File

@ -1,15 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/file_sys/directories/base_directory.h"
#include "core/file_sys/fs.h"
namespace Core::Directories {
BaseDirectory::BaseDirectory() = default;
BaseDirectory::~BaseDirectory() = default;
} // namespace Core::Directories

View File

@ -1,52 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string_view>
#include <vector>
#include "common/types.h"
#include "common/va_ctx.h"
#include "core/libraries/kernel/file_system.h"
#include "core/libraries/kernel/orbis_error.h"
namespace Libraries::Kernel {
struct OrbisKernelStat;
struct OrbisKernelIovec;
struct OrbisKernelDirent;
} // namespace Libraries::Kernel
namespace Core::Directories {
class BaseDirectory {
public:
explicit BaseDirectory();
virtual ~BaseDirectory() = 0;
virtual s64 read(void* buf, u64 nbytes) {
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 lseek(s64 offset, s32 whence) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) {
return ORBIS_KERNEL_ERROR_EBADF;
}
};
} // namespace Core::Directories

View File

@ -1,140 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/file_sys/directories/normal_directory.h"
#include "core/file_sys/fs.h"
namespace Core::Directories {
std::shared_ptr<BaseDirectory> NormalDirectory::Create(std::string_view guest_directory) {
return std::static_pointer_cast<BaseDirectory>(
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;
}
}
s64 NormalDirectory::read(void* buf, u64 nbytes) {
// Nothing left to read.
if (file_offset >= directory_size) {
return ORBIS_OK;
}
const s64 remaining_data = directory_size - file_offset;
const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes;
std::memcpy(buf, data_buffer.data() + file_offset, bytes);
file_offset += bytes;
return bytes;
}
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;
}
s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
stat->st_mode = 0000777u | 0040000u;
stat->st_size = directory_size;
stat->st_blksize = 0x8000;
stat->st_blocks = 8;
return ORBIS_OK;
}
s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
if (basep != nullptr) {
*basep = file_offset;
}
// read behaves identically to getdents for normal directories.
return read(buf, nbytes);
}
} // namespace Core::Directories

View File

@ -1,45 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string_view>
#include <vector>
#include "common/types.h"
#include "core/file_sys/directories/base_directory.h"
#include "core/libraries/kernel/orbis_error.h"
namespace Core::Directories {
class NormalDirectory final : public BaseDirectory {
public:
static std::shared_ptr<BaseDirectory> Create(std::string_view guest_path);
explicit NormalDirectory(std::string_view guest_path);
~NormalDirectory() override = default;
virtual s64 read(void* buf, u64 count) 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 count, s64* basep) override;
private:
static constexpr s32 MAX_LENGTH = 255;
static constexpr s64 DIRECTORY_ALIGNMENT = 0x200;
struct NormalDirectoryDirent {
u32 d_fileno;
u16 d_reclen;
u8 d_type;
u8 d_namlen;
char d_name[MAX_LENGTH + 1];
};
u64 directory_size = 0;
s64 file_offset = 0;
std::vector<u8> data_buffer;
std::vector<NormalDirectoryDirent> dirents;
};
} // namespace Core::Directories

View File

@ -1,220 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/file_sys/directories/pfs_directory.h"
#include "core/file_sys/fs.h"
namespace Core::Directories {
std::shared_ptr<BaseDirectory> PfsDirectory::Create(std::string_view guest_directory) {
return std::static_pointer_cast<BaseDirectory>(std::make_shared<PfsDirectory>(guest_directory));
}
PfsDirectory::PfsDirectory(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),
8);
// To handle some obscure dirents_index behavior,
// keep track of the "actual" length of this directory.
directory_content_size += dirent.d_reclen;
});
directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT);
}
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;
}
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;
}
s64 PfsDirectory::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 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 s64 bytes_read = readv(iov, iovcnt);
dirents_index = old_dirent_index;
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;
stat->st_blksize = 0x10000;
stat->st_blocks = 0x80;
return ORBIS_OK;
}
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 (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);
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;
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;
break;
}
dirent = dirents[++dirents_index];
}
return bytes_written;
}
} // namespace Core::Directories

View File

@ -1,53 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <string_view>
#include <vector>
#include "common/types.h"
#include "core/file_sys/directories/base_directory.h"
#include "core/libraries/kernel/orbis_error.h"
namespace Core::Directories {
class PfsDirectory final : public BaseDirectory {
public:
static std::shared_ptr<BaseDirectory> Create(std::string_view guest_path);
explicit PfsDirectory(std::string_view guest_path);
~PfsDirectory() 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 s32 DIRECTORY_ALIGNMENT = 0x10000;
struct PfsDirectoryDirent {
u32 d_fileno;
u32 d_type;
u32 d_namlen;
u32 d_reclen;
char d_name[MAX_LENGTH + 1];
};
struct NormalDirectoryDirent {
u32 d_fileno;
u16 d_reclen;
u8 d_type;
u8 d_namlen;
char d_name[MAX_LENGTH + 1];
};
u64 directory_size = 0;
u64 directory_content_size = 0;
s64 dirents_index = 0;
std::vector<PfsDirectoryDirent> dirents;
};
} // namespace Core::Directories

View File

@ -10,7 +10,6 @@
#include <tsl/robin_map.h>
#include "common/io_file.h"
#include "common/logging/formatter.h"
#include "core/file_sys/directories/base_directory.h"
namespace Libraries::Net {
struct Socket;

View File

@ -18,8 +18,6 @@
#include "core/file_sys/devices/random_device.h"
#include "core/file_sys/devices/rng_device.h"
#include "core/file_sys/devices/srandom_device.h"
#include "core/file_sys/directories/normal_directory.h"
#include "core/file_sys/directories/pfs_directory.h"
#include "core/file_sys/fs.h"
#include "core/libraries/kernel/file_system.h"

View File

@ -5,7 +5,7 @@
#include "common/assert.h"
#include "common/config.h"
#include "common/debug.h"
#include "core/file_sys/fs.h"
#include "core/file_sys/quasifs/quasifs.h"
#include "core/libraries/kernel/memory.h"
#include "core/libraries/kernel/orbis_error.h"
#include "core/libraries/kernel/process.h"
@ -555,14 +555,19 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
}
// Get the file to map
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(fd);
if (file == nullptr) {
auto* qfs = Common::Singleton<QuasiFS::QFS>::Instance();
auto file = qfs->GetHandle(fd);
if (nullptr == file) {
LOG_WARNING(Kernel_Vmm, "Invalid file for mmap, fd {}", fd);
return ORBIS_KERNEL_ERROR_EBADF;
}
if (file->type != Core::FileSys::FileType::Regular) {
if (nullptr == file->node) {
return ORBIS_KERNEL_ERROR_EBADF;
}
if (!file->node->is_file()) {
LOG_WARNING(Kernel_Vmm, "Unsupported file type for mmap, fd {}", fd);
return ORBIS_KERNEL_ERROR_EBADF;
}
@ -572,10 +577,14 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
prot |= MemoryProt::CpuRead;
}
const auto handle = file->f.GetFileMapping();
const auto handle = file->host_fd;
if (False(file->f.GetAccessMode() & Common::FS::FileAccessMode::Write) ||
False(file->f.GetAccessMode() & Common::FS::FileAccessMode::Append)) {
if (handle < 0) {
LOG_CRITICAL(Kernel_Vmm, "Descriptor does not have host-bound file associated for mmap");
return ORBIS_KERNEL_ERROR_EBADF;
}
if (0 == (file->write | file->append)) {
// If the file does not have write access, ensure prot does not contain write permissions.
// On real hardware, these mappings succeed, but the memory cannot be written to.
prot &= ~MemoryProt::CpuWrite;