diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f503832..56760be3 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -502,6 +502,7 @@ set(COMMON src/common/logging/backend.cpp src/common/types.h src/common/uint128.h src/common/unique_function.h + src/common/va_ctx.h src/common/version.h src/common/ntapi.h src/common/ntapi.cpp @@ -526,6 +527,12 @@ set(CORE src/core/aerolib/stubs.cpp src/core/crypto/crypto.cpp src/core/crypto/crypto.h src/core/crypto/keys.h + src/core/devices/base_device.cpp + src/core/devices/base_device.h + src/core/devices/ioccom.h + src/core/devices/logger.cpp + src/core/devices/logger.h + src/core/devices/nop_device.h src/core/file_format/pfs.h src/core/file_format/pkg.cpp src/core/file_format/pkg.h diff --git a/src/common/io_file.cpp b/src/common/io_file.cpp index dd3a40ca..067010a2 100644 --- a/src/common/io_file.cpp +++ b/src/common/io_file.cpp @@ -377,16 +377,18 @@ bool IOFile::Seek(s64 offset, SeekOrigin origin) const { return false; } - u64 size = GetSize(); - if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; - } else if (origin == SeekOrigin::End && offset > 0) { - LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); - return false; + if (False(file_access_mode & (FileAccessMode::Write | FileAccessMode::Append))) { + u64 size = GetSize(); + if (origin == SeekOrigin::CurrentPosition && Tell() + offset > size) { + LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); + return false; + } else if (origin == SeekOrigin::SetOrigin && (u64)offset > size) { + LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); + return false; + } else if (origin == SeekOrigin::End && offset > 0) { + LOG_ERROR(Common_Filesystem, "Seeking past the end of the file"); + return false; + } } errno = 0; diff --git a/src/common/io_file.h b/src/common/io_file.h index 8fed4981..feb2110a 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -10,6 +10,7 @@ #include "common/concepts.h" #include "common/types.h" +#include "enum.h" namespace Common::FS { @@ -42,6 +43,7 @@ enum class FileAccessMode { */ ReadAppend = Read | Append, }; +DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode); enum class FileType { BinaryFile, diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 632b2b32..75c61a18 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -69,6 +69,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Common, Memory) \ CLS(Core) \ SUB(Core, Linker) \ + SUB(Core, Devices) \ CLS(Config) \ CLS(Debug) \ CLS(Kernel) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index e7e91882..a0e7d021 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -35,6 +35,7 @@ enum class Class : u8 { Common_Memory, ///< Memory mapping and management functions Core, ///< LLE emulation core Core_Linker, ///< The module linker + Core_Devices, ///< Devices emulation Config, ///< Emulator configuration (including commandline) Debug, ///< Debugging tools Kernel, ///< The HLE implementation of the PS4 kernel. diff --git a/src/common/va_ctx.h b/src/common/va_ctx.h new file mode 100644 index 00000000..e0b8c0ba --- /dev/null +++ b/src/common/va_ctx.h @@ -0,0 +1,111 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include "common/types.h" + +#define VA_ARGS \ + uint64_t rdi, uint64_t rsi, uint64_t rdx, uint64_t rcx, uint64_t r8, uint64_t r9, \ + uint64_t overflow_arg_area, __m128 xmm0, __m128 xmm1, __m128 xmm2, __m128 xmm3, \ + __m128 xmm4, __m128 xmm5, __m128 xmm6, __m128 xmm7, ... + +#define VA_CTX(ctx) \ + alignas(16)::Common::VaCtx ctx{}; \ + (ctx).reg_save_area.gp[0] = rdi; \ + (ctx).reg_save_area.gp[1] = rsi; \ + (ctx).reg_save_area.gp[2] = rdx; \ + (ctx).reg_save_area.gp[3] = rcx; \ + (ctx).reg_save_area.gp[4] = r8; \ + (ctx).reg_save_area.gp[5] = r9; \ + (ctx).reg_save_area.fp[0] = xmm0; \ + (ctx).reg_save_area.fp[1] = xmm1; \ + (ctx).reg_save_area.fp[2] = xmm2; \ + (ctx).reg_save_area.fp[3] = xmm3; \ + (ctx).reg_save_area.fp[4] = xmm4; \ + (ctx).reg_save_area.fp[5] = xmm5; \ + (ctx).reg_save_area.fp[6] = xmm6; \ + (ctx).reg_save_area.fp[7] = xmm7; \ + (ctx).va_list.reg_save_area = &(ctx).reg_save_area; \ + (ctx).va_list.gp_offset = offsetof(::Common::VaRegSave, gp); \ + (ctx).va_list.fp_offset = offsetof(::Common::VaRegSave, fp); \ + (ctx).va_list.overflow_arg_area = &overflow_arg_area; + +namespace Common { + +// https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure + +struct VaList { + u32 gp_offset; + u32 fp_offset; + void* overflow_arg_area; + void* reg_save_area; +}; + +struct VaRegSave { + u64 gp[6]; + __m128 fp[8]; +}; + +struct VaCtx { + VaRegSave reg_save_area; + VaList va_list; +}; + +template +T vaArgRegSaveAreaGp(VaList* l) { + auto* addr = reinterpret_cast(static_cast(l->reg_save_area) + l->gp_offset); + l->gp_offset += Size; + return *addr; +} +template +T vaArgOverflowArgArea(VaList* l) { + auto ptr = ((reinterpret_cast(l->overflow_arg_area) + (Align - 1)) & ~(Align - 1)); + auto* addr = reinterpret_cast(ptr); + l->overflow_arg_area = reinterpret_cast(ptr + Size); + return *addr; +} + +template +T vaArgRegSaveAreaFp(VaList* l) { + auto* addr = reinterpret_cast(static_cast(l->reg_save_area) + l->fp_offset); + l->fp_offset += Size; + return *addr; +} + +inline int vaArgInteger(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +inline long long vaArgLongLong(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} +inline long vaArgLong(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +inline double vaArgDouble(VaList* l) { + if (l->fp_offset <= 160) { + return vaArgRegSaveAreaFp(l); + } + return vaArgOverflowArgArea(l); +} + +template +T* vaArgPtr(VaList* l) { + if (l->gp_offset <= 40) { + return vaArgRegSaveAreaGp(l); + } + return vaArgOverflowArgArea(l); +} + +} // namespace Common diff --git a/src/core/devices/base_device.cpp b/src/core/devices/base_device.cpp new file mode 100644 index 00000000..4f91c81c --- /dev/null +++ b/src/core/devices/base_device.cpp @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "base_device.h" + +namespace Core::Devices { + +BaseDevice::BaseDevice() = default; + +BaseDevice::~BaseDevice() = default; + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/base_device.h b/src/core/devices/base_device.h new file mode 100644 index 00000000..351af82b --- /dev/null +++ b/src/core/devices/base_device.h @@ -0,0 +1,72 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/types.h" +#include "common/va_ctx.h" + +namespace Libraries::Kernel { +struct OrbisKernelStat; +struct SceKernelIovec; +} // namespace Libraries::Kernel + +namespace Core::Devices { + +class BaseDevice { +public: + explicit BaseDevice(); + + virtual ~BaseDevice() = 0; + + virtual int ioctl(u64 cmd, Common::VaCtx* args) { + return ORBIS_KERNEL_ERROR_ENOTTY; + } + + virtual s64 write(const void* buf, size_t nbytes) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 lseek(s64 offset, int whence) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 read(void* buf, size_t nbytes) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual int fstat(Libraries::Kernel::OrbisKernelStat* sb) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s32 fsync() { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual int ftruncate(s64 length) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual int getdents(void* buf, u32 nbytes, s64* basep) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + virtual s64 pwrite(const void* buf, size_t nbytes, u64 offset) { + return ORBIS_KERNEL_ERROR_EBADF; + } +}; + +} // namespace Core::Devices diff --git a/src/core/devices/ioccom.h b/src/core/devices/ioccom.h new file mode 100644 index 00000000..671ee33d --- /dev/null +++ b/src/core/devices/ioccom.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +/*- + * Copyright (c) 1982, 1986, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ioccom.h 8.2 (Berkeley) 3/28/94 + * $FreeBSD$ + */ + +#define IOCPARM_SHIFT 13 /* number of bits for ioctl size */ +#define IOCPARM_MASK ((1 << IOCPARM_SHIFT) - 1) /* parameter length mask */ +#define IOCPARM_LEN(x) (((x) >> 16) & IOCPARM_MASK) +#define IOCBASECMD(x) ((x) & ~(IOCPARM_MASK << 16)) +#define IOCGROUP(x) (((x) >> 8) & 0xff) + +#define IOCPARM_MAX (1 << IOCPARM_SHIFT) /* max size of ioctl */ +#define IOC_VOID 0x20000000 /* no parameters */ +#define IOC_OUT 0x40000000 /* copy out parameters */ +#define IOC_IN 0x80000000 /* copy in parameters */ +#define IOC_INOUT (IOC_IN | IOC_OUT) +#define IOC_DIRMASK (IOC_VOID | IOC_OUT | IOC_IN) + +#define _IOC(inout, group, num, len) \ + ((unsigned long)((inout) | (((len) & IOCPARM_MASK) << 16) | ((group) << 8) | (num))) +#define _IO(g, n) _IOC(IOC_VOID, (g), (n), 0) +#define _IOWINT(g, n) _IOC(IOC_VOID, (g), (n), sizeof(int)) +#define _IOR(g, n, t) _IOC(IOC_OUT, (g), (n), sizeof(t)) +#define _IOW(g, n, t) _IOC(IOC_IN, (g), (n), sizeof(t)) +/* this should be _IORW, but stdio got there first */ +#define _IOWR(g, n, t) _IOC(IOC_INOUT, (g), (n), sizeof(t)) + +/* +# Simple parse of ioctl cmd +def parse(v): + print('inout', (v >> 24 & 0xFF)) + print('len', hex(v >> 16 & 0xFF)) + print('group', chr(v >> 8 & 0xFF)) + print('num', hex(v & 0xFF)) +*/ diff --git a/src/core/devices/logger.cpp b/src/core/devices/logger.cpp new file mode 100644 index 00000000..bf5a2838 --- /dev/null +++ b/src/core/devices/logger.cpp @@ -0,0 +1,64 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "core/libraries/kernel/file_system.h" +#include "logger.h" + +namespace Core::Devices { + +Logger::Logger(std::string prefix, bool is_err) : prefix(std::move(prefix)), is_err(is_err) {} + +Logger::~Logger() = default; + +s64 Logger::write(const void* buf, size_t nbytes) { + log(static_cast(buf), nbytes); + return nbytes; +} +size_t Logger::writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) { + for (int i = 0; i < iovcnt; i++) { + log(static_cast(iov[i].iov_base), iov[i].iov_len); + } + return iovcnt; +} + +s64 Logger::pwrite(const void* buf, size_t nbytes, u64 offset) { + log(static_cast(buf), nbytes); + return nbytes; +} + +s32 Logger::fsync() { + log_flush(); + return 0; +} + +void Logger::log(const char* buf, size_t nbytes) { + std::scoped_lock lock{mtx}; + const char* end = buf + nbytes; + for (const char* it = buf; it < end; ++it) { + char c = *it; + if (c == '\r') { + continue; + } + if (c == '\n') { + log_flush(); + continue; + } + buffer.push_back(c); + } +} + +void Logger::log_flush() { + std::scoped_lock lock{mtx}; + if (buffer.empty()) { + return; + } + if (is_err) { + LOG_ERROR(Tty, "[{}] {}", prefix, std::string_view{buffer}); + } else { + LOG_INFO(Tty, "[{}] {}", prefix, std::string_view{buffer}); + } + buffer.clear(); +} + +} // namespace Core::Devices \ No newline at end of file diff --git a/src/core/devices/logger.h b/src/core/devices/logger.h new file mode 100644 index 00000000..bfb07f33 --- /dev/null +++ b/src/core/devices/logger.h @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "base_device.h" + +#include +#include +#include + +namespace Core::Devices { + +class Logger final : BaseDevice { + std::string prefix; + bool is_err; + + std::recursive_mutex mtx; + std::vector buffer; + +public: + explicit Logger(std::string prefix, bool is_err); + + ~Logger() override; + + s64 write(const void* buf, size_t nbytes) override; + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override; + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override; + + s32 fsync() override; + +private: + void log(const char* buf, size_t nbytes); + void log_flush(); +}; + +} // namespace Core::Devices diff --git a/src/core/devices/nop_device.h b/src/core/devices/nop_device.h new file mode 100644 index 00000000..a75b92f1 --- /dev/null +++ b/src/core/devices/nop_device.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include "base_device.h" + +namespace Core::Devices { + +class NopDevice final : BaseDevice { + u32 handle; + +public: + explicit NopDevice(u32 handle) : handle(handle) {} + + ~NopDevice() override = default; + + int ioctl(u64 cmd, Common::VaCtx* args) override { + return 0; + } + s64 write(const void* buf, size_t nbytes) override { + return 0; + } + size_t readv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override { + return 0; + } + size_t writev(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt) override { + return 0; + } + s64 preadv(const Libraries::Kernel::SceKernelIovec* iov, int iovcnt, u64 offset) override { + return 0; + } + s64 lseek(s64 offset, int whence) override { + return 0; + } + s64 read(void* buf, size_t nbytes) override { + return 0; + } + int fstat(Libraries::Kernel::OrbisKernelStat* sb) override { + return 0; + } + s32 fsync() override { + return 0; + } + int ftruncate(s64 length) override { + return 0; + } + int getdents(void* buf, u32 nbytes, s64* basep) override { + return 0; + } + s64 pwrite(const void* buf, size_t nbytes, u64 offset) override { + return 0; + } +}; + +} // namespace Core::Devices diff --git a/src/core/file_sys/fs.cpp b/src/core/file_sys/fs.cpp index 769940cf..0fdbb278 100644 --- a/src/core/file_sys/fs.cpp +++ b/src/core/file_sys/fs.cpp @@ -4,12 +4,12 @@ #include #include "common/config.h" #include "common/string_util.h" +#include "core/devices/logger.h" +#include "core/devices/nop_device.h" #include "core/file_sys/fs.h" namespace Core::FileSys { -constexpr int RESERVED_HANDLES = 3; // First 3 handles are stdin,stdout,stderr - void MntPoints::Mount(const std::filesystem::path& host_folder, const std::string& guest_folder, bool read_only) { std::scoped_lock lock{m_mutex}; @@ -135,7 +135,6 @@ int HandleTable::CreateHandle() { std::scoped_lock lock{m_mutex}; auto* file = new File{}; - file->is_directory = false; file->is_opened = false; int existingFilesNum = m_files.size(); @@ -143,23 +142,23 @@ int HandleTable::CreateHandle() { for (int index = 0; index < existingFilesNum; index++) { if (m_files.at(index) == nullptr) { m_files[index] = file; - return index + RESERVED_HANDLES; + return index; } } m_files.push_back(file); - return m_files.size() + RESERVED_HANDLES - 1; + return m_files.size() - 1; } void HandleTable::DeleteHandle(int d) { std::scoped_lock lock{m_mutex}; - delete m_files.at(d - RESERVED_HANDLES); - m_files[d - RESERVED_HANDLES] = nullptr; + delete m_files.at(d); + m_files[d] = nullptr; } File* HandleTable::GetFile(int d) { std::scoped_lock lock{m_mutex}; - return m_files.at(d - RESERVED_HANDLES); + return m_files.at(d); } File* HandleTable::GetFile(const std::filesystem::path& host_name) { @@ -171,4 +170,20 @@ File* HandleTable::GetFile(const std::filesystem::path& host_name) { return nullptr; } +void HandleTable::CreateStdHandles() { + auto setup = [this](const char* path, auto* device) { + int fd = CreateHandle(); + auto* file = GetFile(fd); + file->is_opened = true; + file->type = FileType::Device; + file->m_guest_name = path; + file->device = + std::shared_ptr{reinterpret_cast(device)}; + }; + // order matters + setup("/dev/stdin", new Devices::NopDevice(0)); // stdin + setup("/dev/stdout", new Devices::Logger("stdout", false)); // stdout + setup("/dev/stderr", new Devices::Logger("stderr", true)); // stderr +} + } // namespace Core::FileSys diff --git a/src/core/file_sys/fs.h b/src/core/file_sys/fs.h index eeaeaf78..b0153c16 100644 --- a/src/core/file_sys/fs.h +++ b/src/core/file_sys/fs.h @@ -9,6 +9,7 @@ #include #include #include "common/io_file.h" +#include "core/devices/base_device.h" namespace Core::FileSys { @@ -55,15 +56,22 @@ struct DirEntry { bool isFile; }; +enum class FileType { + Regular, // standard file + Directory, + Device, +}; + struct File { std::atomic_bool is_opened{}; - std::atomic_bool is_directory{}; + std::atomic type{FileType::Regular}; std::filesystem::path m_host_name; std::string m_guest_name; Common::FS::IOFile f; std::vector dirents; u32 dirents_index; std::mutex m_mutex; + std::shared_ptr device; // only valid for type == Device }; class HandleTable { @@ -76,6 +84,8 @@ public: File* GetFile(int d); File* GetFile(const std::filesystem::path& host_name); + void CreateStdHandles(); + private: std::vector m_files; std::mutex m_mutex; diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index 1b95e527..447467cb 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -11,6 +11,39 @@ #include "core/libraries/libs.h" #include "kernel.h" +#include +#include + +#include "core/devices/logger.h" +#include "core/devices/nop_device.h" + +namespace D = Core::Devices; +using FactoryDevice = std::function(u32, const char*, int, u16)>; + +#define GET_DEVICE_FD(fd) \ + [](u32, const char*, int, u16) { \ + return Common::Singleton::Instance()->GetFile(fd)->device; \ + } + +// prefix path, only dev devices +static std::map available_device = { + // clang-format off + {"/dev/stdin", GET_DEVICE_FD(0)}, + {"/dev/stdout", GET_DEVICE_FD(1)}, + {"/dev/stderr", GET_DEVICE_FD(2)}, + + {"/dev/fd/0", GET_DEVICE_FD(0)}, + {"/dev/fd/1", GET_DEVICE_FD(1)}, + {"/dev/fd/2", GET_DEVICE_FD(2)}, + + {"/dev/deci_stdin", GET_DEVICE_FD(0)}, + {"/dev/deci_stdout", GET_DEVICE_FD(1)}, + {"/dev/deci_stderr", GET_DEVICE_FD(2)}, + + {"/dev/null", GET_DEVICE_FD(0)}, // fd0 (stdin) is a nop device + // clang-format on +}; + namespace Libraries::Kernel { auto GetDirectoryEntries(const std::filesystem::path& path) { @@ -24,8 +57,8 @@ auto GetDirectoryEntries(const std::filesystem::path& path) { return files; } -int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) { - LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", path, flags, mode); +int PS4_SYSV_ABI sceKernelOpen(const char* raw_path, int flags, u16 mode) { + LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {}", raw_path, flags, mode); auto* h = Common::Singleton::Instance(); auto* mnt = Common::Singleton::Instance(); @@ -44,22 +77,35 @@ int PS4_SYSV_ABI sceKernelOpen(const char* path, int flags, u16 mode) { bool direct = (flags & ORBIS_KERNEL_O_DIRECT) != 0; bool directory = (flags & ORBIS_KERNEL_O_DIRECTORY) != 0; - if (std::string_view{path} == "/dev/console") { + std::string_view path{raw_path}; + + if (path == "/dev/console") { return 2000; } - if (std::string_view{path} == "/dev/deci_tty6") { + if (path == "/dev/deci_tty6") { return 2001; } - if (std::string_view{path} == "/dev/stdout") { - return 2002; - } - if (std::string_view{path} == "/dev/urandom") { + if (path == "/dev/urandom") { return 2003; } + u32 handle = h->CreateHandle(); auto* file = h->GetFile(handle); + + if (path.starts_with("/dev/")) { + for (const auto& [prefix, factory] : available_device) { + if (path.starts_with(prefix)) { + file->is_opened = true; + file->type = Core::FileSys::FileType::Device; + file->m_guest_name = path; + file->device = factory(handle, path.data(), flags, mode); + return handle; + } + } + } + if (directory) { - file->is_directory = true; + file->type = Core::FileSys::FileType::Directory; file->m_guest_name = path; file->m_host_name = mnt->GetHostPath(file->m_guest_name); if (!std::filesystem::is_directory(file->m_host_name)) { // directory doesn't exist @@ -135,11 +181,12 @@ int PS4_SYSV_ABI sceKernelClose(int d) { if (file == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } - if (!file->is_directory) { + if (file->type == Core::FileSys::FileType::Regular) { file->f.Close(); } file->is_opened = false; LOG_INFO(Kernel_Fs, "Closing {}", file->m_guest_name); + // FIXME: Lock file mutex before deleting it? h->DeleteHandle(d); return ORBIS_OK; } @@ -155,14 +202,6 @@ int PS4_SYSV_ABI posix_close(int d) { } size_t PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) { - if (d <= 2) { // stdin,stdout,stderr - char* str = strdup((const char*)buf); - if (str[nbytes - 1] == '\n') - str[nbytes - 1] = 0; - LOG_INFO(Tty, "{}", str); - free(str); - return nbytes; - } auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); if (file == nullptr) { @@ -170,6 +209,9 @@ size_t PS4_SYSV_ABI sceKernelWrite(int d, const void* buf, size_t nbytes) { } std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->write(buf, nbytes); + } return file->f.WriteRaw(buf, nbytes); } @@ -207,17 +249,63 @@ int PS4_SYSV_ABI sceKernelUnlink(const char* path) { size_t PS4_SYSV_ABI _readv(int d, const SceKernelIovec* iov, int iovcnt) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); - size_t total_read = 0; + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } + std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + int r = file->device->readv(iov, iovcnt); + if (r < 0) { + ErrSceToPosix(r); + return -1; + } + return r; + } + size_t total_read = 0; for (int i = 0; i < iovcnt; i++) { total_read += file->f.ReadRaw(iov[i].iov_base, iov[i].iov_len); } return total_read; } +size_t PS4_SYSV_ABI _writev(int fd, const SceKernelIovec* iov, int iovcn) { + if (fd == 1) { + size_t total_written = 0; + for (int i = 0; i < iovcn; i++) { + total_written += ::fwrite(iov[i].iov_base, 1, iov[i].iov_len, stdout); + } + return total_written; + } + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(fd); + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + std::scoped_lock lk{file->m_mutex}; + + if (file->type == Core::FileSys::FileType::Device) { + return file->device->writev(iov, iovcn); + } + size_t total_written = 0; + for (int i = 0; i < iovcn; i++) { + total_written += file->f.WriteRaw(iov[i].iov_base, iov[i].iov_len); + } + return total_written; +} + s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(d); + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->lseek(offset, whence); + } Common::FS::SeekOrigin origin{}; if (whence == 0) { @@ -228,7 +316,6 @@ s64 PS4_SYSV_ABI sceKernelLseek(int d, s64 offset, int whence) { origin = Common::FS::SeekOrigin::End; } - std::scoped_lock lk{file->m_mutex}; if (!file->f.Seek(offset, origin)) { LOG_CRITICAL(Kernel_Fs, "sceKernelLseek: failed to seek"); return ORBIS_KERNEL_ERROR_EINVAL; @@ -261,6 +348,9 @@ s64 PS4_SYSV_ABI sceKernelRead(int d, void* buf, size_t nbytes) { } std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->read(buf, nbytes); + } return file->f.ReadRaw(buf, nbytes); } @@ -409,7 +499,13 @@ int PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) { int PS4_SYSV_ABI sceKernelCheckReachability(const char* path) { auto* mnt = Common::Singleton::Instance(); - const auto path_name = mnt->GetHostPath(path); + std::string_view guest_path{path}; + for (const auto& prefix : available_device | std::views::keys) { + if (guest_path.starts_with(prefix)) { + return ORBIS_OK; + } + } + const auto path_name = mnt->GetHostPath(guest_path); if (!std::filesystem::exists(path_name)) { return ORBIS_KERNEL_ERROR_ENOENT; } @@ -431,6 +527,10 @@ s64 PS4_SYSV_ABI sceKernelPreadv(int d, SceKernelIovec* iov, int iovcnt, s64 off } std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->preadv(iov, iovcnt, offset); + } + const s64 pos = file->f.Tell(); SCOPE_EXIT { file->f.Seek(pos); @@ -466,18 +566,25 @@ int PS4_SYSV_ABI sceKernelFStat(int fd, OrbisKernelStat* sb) { } std::memset(sb, 0, sizeof(OrbisKernelStat)); - if (file->is_directory) { - sb->st_mode = 0000777u | 0040000u; - sb->st_size = 0; - sb->st_blksize = 512; - sb->st_blocks = 0; - // TODO incomplete - } else { + switch (file->type) { + case Core::FileSys::FileType::Device: + return file->device->fstat(sb); + case Core::FileSys::FileType::Regular: sb->st_mode = 0000777u | 0100000u; sb->st_size = file->f.GetSize(); sb->st_blksize = 512; sb->st_blocks = (sb->st_size + 511) / 512; // TODO incomplete + break; + case Core::FileSys::FileType::Directory: + sb->st_mode = 0000777u | 0040000u; + sb->st_size = 0; + sb->st_blksize = 512; + sb->st_blocks = 0; + // TODO incomplete + break; + default: + UNREACHABLE(); } return ORBIS_OK; } @@ -495,6 +602,13 @@ int PS4_SYSV_ABI posix_fstat(int fd, OrbisKernelStat* sb) { s32 PS4_SYSV_ABI sceKernelFsync(int fd) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + if (file->type == Core::FileSys::FileType::Device) { + return file->device->fsync(); + } file->f.Flush(); return ORBIS_OK; } @@ -517,6 +631,10 @@ int PS4_SYSV_ABI sceKernelFtruncate(int fd, s64 length) { return ORBIS_KERNEL_ERROR_EBADF; } + if (file->type == Core::FileSys::FileType::Device) { + return file->device->ftruncate(length); + } + if (file->m_host_name.empty()) { return ORBIS_KERNEL_ERROR_EACCES; } @@ -538,10 +656,15 @@ static int GetDents(int fd, char* buf, int nbytes, s64* basep) { if (file == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; } + if (file->type != Core::FileSys::FileType::Device) { + return file->device->getdents(buf, nbytes, basep); + } + if (file->dirents_index == file->dirents.size()) { return ORBIS_OK; } - if (!file->is_directory || nbytes < 512 || file->dirents_index > file->dirents.size()) { + if (file->type != Core::FileSys::FileType::Directory || nbytes < 512 || + file->dirents_index > file->dirents.size()) { return ORBIS_KERNEL_ERROR_EINVAL; } const auto& entry = file->dirents.at(file->dirents_index++); @@ -586,6 +709,10 @@ s64 PS4_SYSV_ABI sceKernelPwrite(int d, void* buf, size_t nbytes, s64 offset) { } std::scoped_lock lk{file->m_mutex}; + + if (file->type == Core::FileSys::FileType::Device) { + return file->device->pwrite(buf, nbytes, offset); + } const s64 pos = file->f.Tell(); SCOPE_EXIT { file->f.Seek(pos); @@ -637,6 +764,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite); LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, _readv); + LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, _writev); LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek); LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek); LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek); diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index 4028116e..0b4e89fc 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -9,6 +9,9 @@ #include "common/logging/log.h" #include "common/polyfill_thread.h" #include "common/thread.h" +#include "common/va_ctx.h" +#include "core/file_sys/fs.h" +#include "core/libraries/error_codes.h" #include "core/libraries/kernel/equeue.h" #include "core/libraries/kernel/file_system.h" #include "core/libraries/kernel/kernel.h" @@ -24,6 +27,7 @@ #ifdef _WIN64 #include #endif +#include namespace Libraries::Kernel { @@ -65,19 +69,6 @@ static PS4_SYSV_ABI void stack_chk_fail() { UNREACHABLE(); } -struct iovec { - void* iov_base; /* Base address. */ - size_t iov_len; /* Length. */ -}; - -size_t PS4_SYSV_ABI _writev(int fd, const struct iovec* iov, int iovcn) { - size_t total_written = 0; - for (int i = 0; i < iovcn; i++) { - total_written += ::fwrite(iov[i].iov_base, 1, iov[i].iov_len, stdout); - } - return total_written; -} - static thread_local int g_posix_errno = 0; int* PS4_SYSV_ABI __Error() { @@ -142,24 +133,33 @@ void PS4_SYSV_ABI sceLibcHeapGetTraceInfo(HeapInfoInfo* info) { } s64 PS4_SYSV_ABI ps4__write(int d, const char* buf, std::size_t nbytes) { - if (d <= 2) { // stdin,stdout,stderr - std::string_view str{buf}; - if (str[nbytes - 1] == '\n') { - str = str.substr(0, nbytes - 1); - } - LOG_INFO(Tty, "{}", str); - return nbytes; + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(d); + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; } - LOG_ERROR(Kernel, "(STUBBED) called d = {} nbytes = {} ", d, nbytes); - UNREACHABLE(); - return ORBIS_OK; + std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->write(buf, nbytes); + } + return file->f.WriteRaw(buf, nbytes); } s64 PS4_SYSV_ABI ps4__read(int d, void* buf, u64 nbytes) { - ASSERT_MSG(d == 0, "d is not 0!"); - - return static_cast( - strlen(std::fgets(static_cast(buf), static_cast(nbytes), stdin))); + if (d == 0) { + return static_cast( + strlen(std::fgets(static_cast(buf), static_cast(nbytes), stdin))); + } + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(d); + if (file == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } + std::scoped_lock lk{file->m_mutex}; + if (file->type == Core::FileSys::FileType::Device) { + return file->device->read(buf, nbytes); + } + return file->f.ReadRaw(buf, nbytes); } struct OrbisKernelUuid { @@ -189,6 +189,29 @@ int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { return 0; } +int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) { + auto* h = Common::Singleton::Instance(); + auto* file = h->GetFile(fd); + if (file == nullptr) { + LOG_INFO(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} file == nullptr", fd, cmd); + g_posix_errno = POSIX_EBADF; + return -1; + } + if (file->type != Core::FileSys::FileType::Device) { + LOG_WARNING(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} file->type != Device", fd, cmd); + g_posix_errno = ENOTTY; + return -1; + } + VA_CTX(ctx); + int result = file->device->ioctl(cmd, &ctx); + LOG_TRACE(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} result = {}", fd, cmd, result); + if (result < 0) { + ErrSceToPosix(result); + return -1; + } + return result; +} + const char* PS4_SYSV_ABI sceKernelGetFsSandboxRandomWord() { const char* path = "sys"; return path; @@ -219,13 +242,13 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) { Libraries::Kernel::RegisterException(sym); LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", 1, 1, &g_stack_chk_guard); + LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", 1, 1, kernel_ioctl); LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", 1, 1, sceKernelGetFsSandboxRandomWord); LIB_FUNCTION("XVL8So3QJUk", "libkernel", 1, "libkernel", 1, 1, posix_connect); LIB_FUNCTION("6xVpy0Fdq+I", "libkernel", 1, "libkernel", 1, 1, _sigprocmask); LIB_FUNCTION("Xjoosiw+XPI", "libkernel", 1, "libkernel", 1, 1, sceKernelUuidCreate); LIB_FUNCTION("Ou3iL1abvng", "libkernel", 1, "libkernel", 1, 1, stack_chk_fail); LIB_FUNCTION("9BcDykPmo1I", "libkernel", 1, "libkernel", 1, 1, __Error); - LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, _writev); LIB_FUNCTION("DRuBt2pvICk", "libkernel", 1, "libkernel", 1, 1, ps4__read); LIB_FUNCTION("k+AXqu2-eBc", "libkernel", 1, "libkernel", 1, 1, posix_getpagesize); LIB_FUNCTION("k+AXqu2-eBc", "libScePosix", 1, "libkernel", 1, 1, posix_getpagesize); diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 15e4ff82..a657ddf9 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -20,7 +20,7 @@ int PS4_SYSV_ABI sceKernelIsNeoMode() { int PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(int* ver) { int version = Common::ElfInfo::Instance().RawFirmwareVer(); *ver = version; - return (version > 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; + return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL; } int PS4_SYSV_ABI sceKernelGetCpumode() { diff --git a/src/emulator.cpp b/src/emulator.cpp index 1d2542d2..60d6e18d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -75,6 +75,9 @@ Emulator::Emulator() { LOG_INFO(Config, "Vulkan rdocMarkersEnable: {}", Config::vkMarkersEnabled()); LOG_INFO(Config, "Vulkan crashDiagnostics: {}", Config::vkCrashDiagnosticEnabled()); + // Create stdin/stdout/stderr + Common::Singleton::Instance()->CreateStdHandles(); + // Defer until after logging is initialized. memory = Core::Memory::Instance(); controller = Common::Singleton::Instance();