diff --git a/CMakeLists.txt b/CMakeLists.txt index c276d3cd3..7e1a972d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -589,6 +589,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h src/core/libraries/np/trophy_ui.h src/core/libraries/np/np_web_api.cpp src/core/libraries/np/np_web_api.h + src/core/libraries/np/np_web_api2.cpp + src/core/libraries/np/np_web_api2.h src/core/libraries/np/np_party.cpp src/core/libraries/np/np_party.h src/core/libraries/np/np_auth.cpp diff --git a/README.md b/README.md index dbb890f87..69ee64b13 100644 --- a/README.md +++ b/README.md @@ -148,9 +148,9 @@ The following firmware modules are supported and must be placed in shadPS4's `sy | Modules | Modules | Modules | Modules | |-------------------------|-------------------------|-------------------------|-------------------------| | libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx | -| libSceJson.sprx | libSceJson2.sprx | libSceLibcInternal.sprx | libSceNgs2.sprx | +| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx | +| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx | | libSceUlt.sprx | | | | - > [!Caution] diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index da683d9d1..d954f8601 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -109,6 +109,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, NpScore) \ SUB(Lib, NpTrophy) \ SUB(Lib, NpWebApi) \ + SUB(Lib, NpWebApi2) \ SUB(Lib, NpProfileDialog) \ SUB(Lib, NpSnsFacebookDialog) \ SUB(Lib, Screenshot) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 513ca4535..ee18cd161 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -76,6 +76,7 @@ enum class Class : u8 { Lib_NpScore, ///< The LibSceNpScore implementation Lib_NpTrophy, ///< The LibSceNpTrophy implementation Lib_NpWebApi, ///< The LibSceWebApi implementation + Lib_NpWebApi2, ///< The LibSceWebApi2 implementation Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation Lib_NpSnsFacebookDialog, ///< The LibSceNpSnsFacebookDialog implementation Lib_Screenshot, ///< The LibSceScreenshot implementation diff --git a/src/core/file_sys/directories/base_directory.cpp b/src/core/file_sys/directories/base_directory.cpp index c709da6a2..75f67577c 100644 --- a/src/core/file_sys/directories/base_directory.cpp +++ b/src/core/file_sys/directories/base_directory.cpp @@ -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 \ No newline at end of file diff --git a/src/core/file_sys/directories/base_directory.h b/src/core/file_sys/directories/base_directory.h index b412865a2..832b8ac40 100644 --- a/src/core/file_sys/directories/base_directory.h +++ b/src/core/file_sys/directories/base_directory.h @@ -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 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; diff --git a/src/core/file_sys/directories/normal_directory.cpp b/src/core/file_sys/directories/normal_directory.cpp index a7d76074a..3ed7c9492 100644 --- a/src/core/file_sys/directories/normal_directory.cpp +++ b/src/core/file_sys/directories/normal_directory.cpp @@ -15,111 +15,30 @@ std::shared_ptr NormalDirectory::Create(std::string_view guest_di std::make_shared(guest_directory)); } -NormalDirectory::NormalDirectory(std::string_view guest_directory) { - auto* mnt = Common::Singleton::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(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(bytes_available, static_cast(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(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; + } + + if ((bytes_written + d_reclen) > aligned_count) + // dirents are aligned to the last full one + break; + + memcpy(static_cast(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; } - // read behaves identically to getdents for normal directories. - return read(buf, nbytes); + + 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::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(static_cast(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(static_cast(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 \ No newline at end of file diff --git a/src/core/file_sys/directories/normal_directory.h b/src/core/file_sys/directories/normal_directory.h index 70e52f581..4fc84cd2a 100644 --- a/src/core/file_sys/directories/normal_directory.h +++ b/src/core/file_sys/directories/normal_directory.h @@ -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 data_buffer; - std::vector dirents; + std::string_view guest_directory{}; + s64 previous_file_offset = -1; + + void RebuildDirents(void); }; } // namespace Core::Directories diff --git a/src/core/file_sys/directories/pfs_directory.cpp b/src/core/file_sys/directories/pfs_directory.cpp index fbd97c019..38ceaf345 100644 --- a/src/core/file_sys/directories/pfs_directory.cpp +++ b/src/core/file_sys/directories/pfs_directory.cpp @@ -15,77 +15,49 @@ std::shared_ptr 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::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(&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; - } - dirents_index = corrected_index; - } else { - // Nothing left to read. - return ORBIS_OK; - } + s64 bytes_available = this->dirent_cache_bin.size() - file_offset; + if (bytes_available <= 0) + return 0; + + bytes_available = std::min(bytes_available, static_cast(nbytes)); + memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available); + + s64 to_fill = + (std::min(directory_size, static_cast(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; } - - 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(buf); - PfsDirectoryDirent dirent = dirents[dirents_index]; - while (bytes_remaining > dirent.d_reclen) { - PfsDirectoryDirent* dirent_to_write = reinterpret_cast(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(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(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(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(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; + + 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; } - dirent = dirents[++dirents_index]; + + 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(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 \ No newline at end of file diff --git a/src/core/file_sys/directories/pfs_directory.h b/src/core/file_sys/directories/pfs_directory.h index 8f3e8d1f5..23b7e1eb0 100644 --- a/src/core/file_sys/directories/pfs_directory.h +++ b/src/core/file_sys/directories/pfs_directory.h @@ -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 dirents; +#pragma pack(pop) }; } // namespace Core::Directories diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 4ac8532db..7f679e7c2 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -41,13 +41,13 @@ #include "core/libraries/np/np_sns_facebook_dialog.h" #include "core/libraries/np/np_trophy.h" #include "core/libraries/np/np_web_api.h" +#include "core/libraries/np/np_web_api2.h" #include "core/libraries/pad/pad.h" #include "core/libraries/playgo/playgo.h" #include "core/libraries/playgo/playgo_dialog.h" #include "core/libraries/random/random.h" #include "core/libraries/razor_cpu/razor_cpu.h" #include "core/libraries/remote_play/remoteplay.h" -#include "core/libraries/rtc/rtc.h" #include "core/libraries/rudp/rudp.h" #include "core/libraries/save_data/dialog/savedatadialog.h" #include "core/libraries/save_data/savedata.h" @@ -71,7 +71,6 @@ #include "core/libraries/web_browser_dialog/webbrowserdialog.h" #include "core/libraries/zlib/zlib_sce.h" #include "fiber/fiber.h" -#include "jpeg/jpegenc.h" namespace Libraries { @@ -101,6 +100,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Np::NpScore::RegisterLib(sym); Libraries::Np::NpTrophy::RegisterLib(sym); Libraries::Np::NpWebApi::RegisterLib(sym); + Libraries::Np::NpWebApi2::RegisterLib(sym); Libraries::Np::NpProfileDialog::RegisterLib(sym); Libraries::Np::NpSnsFacebookDialog::RegisterLib(sym); Libraries::Np::NpAuth::RegisterLib(sym); @@ -118,17 +118,16 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ErrorDialog::RegisterLib(sym); Libraries::ImeDialog::RegisterLib(sym); Libraries::AvPlayer::RegisterLib(sym); - Libraries::Vdec2::RegisterLib(sym); + Libraries::Videodec::RegisterLib(sym); + Libraries::Videodec2::RegisterLib(sym); Libraries::Audio3d::RegisterLib(sym); Libraries::Ime::RegisterLib(sym); Libraries::GameLiveStreaming::RegisterLib(sym); Libraries::SharePlay::RegisterLib(sym); Libraries::Remoteplay::RegisterLib(sym); - Libraries::Videodec::RegisterLib(sym); Libraries::RazorCpu::RegisterLib(sym); Libraries::Move::RegisterLib(sym); Libraries::Fiber::RegisterLib(sym); - Libraries::JpegEnc::RegisterLib(sym); Libraries::Mouse::RegisterLib(sym); Libraries::WebBrowserDialog::RegisterLib(sym); Libraries::Zlib::RegisterLib(sym); @@ -141,7 +140,6 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::CompanionHttpd::RegisterLib(sym); Libraries::CompanionUtil::RegisterLib(sym); Libraries::Voice::RegisterLib(sym); - Libraries::Rtc::RegisterLib(sym); Libraries::Rudp::RegisterLib(sym); Libraries::VrTracker::RegisterLib(sym); diff --git a/src/core/libraries/network/posix_sockets.cpp b/src/core/libraries/network/posix_sockets.cpp index 7992fa217..f2adccf50 100644 --- a/src/core/libraries/network/posix_sockets.cpp +++ b/src/core/libraries/network/posix_sockets.cpp @@ -204,17 +204,18 @@ static int convertOrbisFlagsToPosix(int sock_type, int sce_flags) { // On Windows, MSG_DONTWAIT is not handled natively by recv/send. // This function uses select() with zero timeout to simulate non-blocking behavior. static int socket_is_ready(int sock, bool is_read = true) { - fd_set fds; + fd_set fds{}; FD_ZERO(&fds); FD_SET(sock, &fds); timeval timeout{0, 0}; int res = select(sock + 1, is_read ? &fds : nullptr, is_read ? nullptr : &fds, nullptr, &timeout); - if (res == 0) - return ORBIS_NET_ERROR_EWOULDBLOCK; - else if (res < 0) + if (res == 0) { + *Libraries::Kernel::__Error() = ORBIS_NET_EWOULDBLOCK; + return -1; + } else if (res < 0) { return ConvertReturnErrorCode(res); - + } return res; } diff --git a/src/core/libraries/np/np_web_api2.cpp b/src/core/libraries/np/np_web_api2.cpp new file mode 100644 index 000000000..c03636e73 --- /dev/null +++ b/src/core/libraries/np/np_web_api2.cpp @@ -0,0 +1,331 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/config.h" +#include "common/logging/log.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" +#include "core/libraries/np/np_web_api2.h" +#include "core/libraries/np/np_web_api2_error.h" +#include "core/libraries/system/userservice.h" + +namespace Libraries::Np::NpWebApi2 { + +s32 PS4_SYSV_ABI sceNpWebApi2AbortRequest(s64 request_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddHttpRequestHeader() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddMultipartPart() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2AddWebTraceTag() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +void PS4_SYSV_ABI sceNpWebApi2CheckTimeout() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateMultipartRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2CreateUserContext(s32 lib_ctx_id, + UserService::OrbisUserServiceUserId user_id) { + if (lib_ctx_id >= 0x8000) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID; + } + if (user_id == UserService::ORBIS_USER_SERVICE_USER_ID_INVALID) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}, user_id = {:#x}", lib_ctx_id, + user_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2DeleteRequest(s64 request_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}", request_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2DeleteUserContext(s32 user_ctx_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, user_ctx_id = {:#x}", user_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValue() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetHttpResponseHeaderValueLength() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2GetMemoryPoolStats(s32 lib_ctx_id, + OrbisNpWebApi2MemoryPoolStats* stats) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2Initialize(s32 lib_http_ctx_id, u64 pool_size) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", + lib_http_ctx_id, pool_size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2InitializeForPresence(s32 lib_http_ctx_id, u64 pool_size) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}", + lib_http_ctx_id, pool_size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntCreateRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize(const OrbisNpWebApi2IntInitializeArgs* args) { + if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitializeArgs)) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, + "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}'", + args->lib_http_ctx_id, args->pool_size, args->name); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntInitialize2(const OrbisNpWebApi2IntInitialize2Args* args) { + if (args == nullptr || args->struct_size != sizeof(OrbisNpWebApi2IntInitialize2Args)) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR( + Lib_NpWebApi2, + "(STUBBED) called, lib_http_ctx_id = {:#x}, pool_size = {:#x}, name = '{}', group = {:#x}", + args->lib_http_ctx_id, args->pool_size, args->name, args->push_config_group); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2IntPushEventCreateCtxIndFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventAbortHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreateHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventCreatePushContext() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteFilter() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeleteHandle() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventDeletePushContext() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventRegisterPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventSetHandleTimeout() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventStartPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2PushEventUnregisterPushContextCallback() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2ReadData(s64 request_id, void* data, u64 size) { + if (data == nullptr || size == 0) { + return ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, size = {:#x}", request_id, + size); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SendMultipartRequest() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SendRequest() { + if (!Config::getPSNSignedIn()) { + LOG_INFO(Lib_NpWebApi2, "called, returning PSN signed out."); + return ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN; + } + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SetMultipartContentType() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2SetRequestTimeout(s64 request_id, u32 timeout) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, request_id = {:#x}, timeout = {}", request_id, + timeout); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceNpWebApi2Terminate(s32 lib_ctx_id) { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called, lib_ctx_id = {:#x}", lib_ctx_id); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_A9A31C5F6FBA6620() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_03D22863300D2B73() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_97296F7578AAD541() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI Func_E0DF39A36F087DB9() { + LOG_ERROR(Lib_NpWebApi2, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterLib(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("zpiPsH7dbFQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2AbortRequest); + LIB_FUNCTION("egOOvrnF6mI", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddHttpRequestHeader); + LIB_FUNCTION("Io7kh1LHDoM", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddMultipartPart); + LIB_FUNCTION("MgsTa76wlEk", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2AddWebTraceTag); + LIB_FUNCTION("3Tt9zL3tkoc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CheckTimeout); + LIB_FUNCTION("+nz1Vq-NrDA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2CreateMultipartRequest); + LIB_FUNCTION("3EI-OSJ65Xc", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2CreateRequest); + LIB_FUNCTION("sk54bi6FtYM", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2CreateUserContext); + LIB_FUNCTION("vvzWO-DvG1s", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2DeleteRequest); + LIB_FUNCTION("9X9+cneTGUU", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2DeleteUserContext); + LIB_FUNCTION("hksbskNToEA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetHttpResponseHeaderValue); + LIB_FUNCTION("HwP3aM+c85c", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetHttpResponseHeaderValueLength); + LIB_FUNCTION("Xweb+naPZ8Y", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2GetMemoryPoolStats); + LIB_FUNCTION("+o9816YQhqQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Initialize); + LIB_FUNCTION("dowMWFgowXY", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2InitializeForPresence); + LIB_FUNCTION("qmINYLuqzaA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntCreateRequest); + LIB_FUNCTION("zXaFo7euxsQ", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2IntInitialize); + LIB_FUNCTION("9KSGFMRnp3k", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntInitialize2); + LIB_FUNCTION("2hlBNB96saE", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2IntPushEventCreateCtxIndFilter); + LIB_FUNCTION("1OLgvahaSco", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventAbortHandle); + LIB_FUNCTION("MsaFhR+lPE4", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreateFilter); + LIB_FUNCTION("WV1GwM32NgY", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreateHandle); + LIB_FUNCTION("NNVf18SlbT8", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventCreatePushContext); + LIB_FUNCTION("KJdPcOGmK58", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeleteFilter); + LIB_FUNCTION("fIATVMo4Y1w", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeleteHandle); + LIB_FUNCTION("QafxeZM3WK4", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventDeletePushContext); + LIB_FUNCTION("fY3QqeNkF8k", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventRegisterCallback); + LIB_FUNCTION("lxtHJMwBsaU", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventRegisterPushContextCallback); + LIB_FUNCTION("KWkc6Q3tjXc", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventSetHandleTimeout); + LIB_FUNCTION("AAj9X+4aGYA", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventStartPushContextCallback); + LIB_FUNCTION("hOnIlcGrO6g", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventUnregisterCallback); + LIB_FUNCTION("PmyrbbJSFz0", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2PushEventUnregisterPushContextCallback); + LIB_FUNCTION("OOY9+ObfKec", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2ReadData); + LIB_FUNCTION("NKCwS8+5Fx8", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SendMultipartRequest); + LIB_FUNCTION("lQOCF84lvzw", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2SendRequest); + LIB_FUNCTION("bltDCAskmfE", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SetMultipartContentType); + LIB_FUNCTION("TjAutbrkr60", "libSceNpWebApi2", 1, "libSceNpWebApi2", + sceNpWebApi2SetRequestTimeout); + LIB_FUNCTION("bEvXpcEk200", "libSceNpWebApi2", 1, "libSceNpWebApi2", sceNpWebApi2Terminate); + LIB_FUNCTION("qaMcX2+6ZiA", "libSceNpWebApi2", 1, "libSceNpWebApi2", Func_A9A31C5F6FBA6620); + LIB_FUNCTION("A9IoYzANK3M", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_03D22863300D2B73); + LIB_FUNCTION("lylvdXiq1UE", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_97296F7578AAD541); + LIB_FUNCTION("4N85o28Ifbk", "libSceNpWebApi2AsyncRestricted", 1, "libSceNpWebApi2", + Func_E0DF39A36F087DB9); +}; + +} // namespace Libraries::Np::NpWebApi2 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2.h b/src/core/libraries/np/np_web_api2.h new file mode 100644 index 000000000..1e970d08e --- /dev/null +++ b/src/core/libraries/np/np_web_api2.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::Np::NpWebApi2 { + +struct OrbisNpWebApi2IntInitializeArgs { + s32 lib_http_ctx_id; + s32 reserved; + u64 pool_size; + char* name; + u64 struct_size; +}; + +struct OrbisNpWebApi2IntInitialize2Args { + s32 lib_http_ctx_id; + s32 reserved; + u64 pool_size; + char* name; + u32 push_config_group; + s32 reserved2; + u64 struct_size; +}; + +struct OrbisNpWebApi2MemoryPoolStats { + u64 pool_size; + u64 max_inuse_size; + u64 current_inuse_size; + s32 reserved; +}; + +void RegisterLib(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::Np::NpWebApi2 \ No newline at end of file diff --git a/src/core/libraries/np/np_web_api2_error.h b/src/core/libraries/np/np_web_api2_error.h new file mode 100644 index 000000000..6ab4fdb31 --- /dev/null +++ b/src/core/libraries/np/np_web_api2_error.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "core/libraries/error_codes.h" + +constexpr int ORBIS_NP_WEBAPI2_ERROR_OUT_OF_MEMORY = 0x80553401; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_ARGUMENT = 0x80553402; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_LIB_CONTEXT_ID = 0x80553403; +constexpr int ORBIS_NP_WEBAPI2_ERROR_LIB_CONTEXT_NOT_FOUND = 0x80553404; +constexpr int ORBIS_NP_WEBAPI2_ERROR_USER_CONTEXT_NOT_FOUND = 0x80553405; +constexpr int ORBIS_NP_WEBAPI2_ERROR_REQUEST_NOT_FOUND = 0x80553406; +constexpr int ORBIS_NP_WEBAPI2_ERROR_NOT_SIGNED_IN = 0x80553407; +constexpr int ORBIS_NP_WEBAPI2_ERROR_INVALID_CONTENT_PARAMETER = 0x80553408; +constexpr int ORBIS_NP_WEBAPI2_ERROR_ABORTED = 0x80553409; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_ALREADY_EXIST = 0x8055340a; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_FILTER_NOT_FOUND = 0x8055340b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_EVENT_CALLBACK_NOT_FOUND = 0x8055340c; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_NOT_FOUND = 0x8055340d; +constexpr int ORBIS_NP_WEBAPI2_SIGNED_IN_USER_NOT_FOUND = 0x8055340e; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_BUSY = 0x8055340f; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_BUSY = 0x80553410; +constexpr int ORBIS_NP_WEBAPI2_REQUEST_BUSY = 0x80553411; +constexpr int ORBIS_NP_WEBAPI2_INVALID_HTTP_STATUS_CODE = 0x80553412; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_HTTP_HEADER = 0x80553413; +constexpr int ORBIS_NP_WEBAPI2_PROHIBITED_FUNCTION_CALL = 0x80553414; +constexpr int ORBIS_NP_WEBAPI2_MULTIPART_PART_NOT_FOUND = 0x80553415; +constexpr int ORBIS_NP_WEBAPI2_PARAMETER_TOO_LONG = 0x80553416; +constexpr int ORBIS_NP_WEBAPI2_HANDLE_BUSY = 0x80553417; +constexpr int ORBIS_NP_WEBAPI2_LIB_CONTEXT_MAX = 0x80553418; +constexpr int ORBIS_NP_WEBAPI2_USER_CONTEXT_MAX = 0x80553419; +constexpr int ORBIS_NP_WEBAPI2_AFTER_SEND = 0x8055341a; +constexpr int ORBIS_NP_WEBAPI2_TIMEOUT = 0x8055341b; +constexpr int ORBIS_NP_WEBAPI2_PUSH_CONTEXT_NOT_FOUND = 0x8055341c; \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2.cpp b/src/core/libraries/videodec/videodec2.cpp index a1edb3b68..121818f39 100644 --- a/src/core/libraries/videodec/videodec2.cpp +++ b/src/core/libraries/videodec/videodec2.cpp @@ -8,7 +8,7 @@ #include "core/libraries/videodec/videodec2_impl.h" #include "core/libraries/videodec/videodec_error.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { static constexpr u64 kMinimumMemorySize = 16_MB; ///> Fake minimum memory size for querying @@ -34,7 +34,35 @@ s32 PS4_SYSV_ABI sceVideodec2AllocateComputeQueue(const OrbisVideodec2ComputeConfigInfo* computeCfgInfo, const OrbisVideodec2ComputeMemoryInfo* computeMemInfo, OrbisVideodec2ComputeQueue* computeQueue) { - LOG_INFO(Lib_Vdec2, "called"); + LOG_WARNING(Lib_Vdec2, "called"); + if (!computeCfgInfo || !computeMemInfo || !computeQueue) { + LOG_ERROR(Lib_Vdec2, "Invalid arguments"); + return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER; + } + if (computeCfgInfo->thisSize != sizeof(OrbisVideodec2ComputeConfigInfo) || + computeMemInfo->thisSize != sizeof(OrbisVideodec2ComputeMemoryInfo)) { + LOG_ERROR(Lib_Vdec2, "Invalid struct size"); + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + if (computeCfgInfo->reserved0 != 0 || computeCfgInfo->reserved1 != 0) { + LOG_ERROR(Lib_Vdec2, "Invalid compute config"); + return ORBIS_VIDEODEC2_ERROR_CONFIG_INFO; + } + if (computeCfgInfo->computePipeId > 4) { + LOG_ERROR(Lib_Vdec2, "Invalid compute pipe id"); + return ORBIS_VIDEODEC2_ERROR_COMPUTE_PIPE_ID; + } + if (computeCfgInfo->computeQueueId > 7) { + LOG_ERROR(Lib_Vdec2, "Invalid compute queue id"); + return ORBIS_VIDEODEC2_ERROR_COMPUTE_QUEUE_ID; + } + if (!computeMemInfo->cpuGpuMemory) { + LOG_ERROR(Lib_Vdec2, "Invalid memory pointer"); + return ORBIS_VIDEODEC2_ERROR_MEMORY_POINTER; + } + + // The real library returns a pointer to memory inside cpuGpuMemory + *computeQueue = computeMemInfo->cpuGpuMemory; return ORBIS_OK; } @@ -233,4 +261,4 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) { sceVideodec2GetPictureInfo); } -} // namespace Libraries::Vdec2 +} // namespace Libraries::Videodec2 diff --git a/src/core/libraries/videodec/videodec2.h b/src/core/libraries/videodec/videodec2.h index 0f080129f..0311e4d27 100644 --- a/src/core/libraries/videodec/videodec2.h +++ b/src/core/libraries/videodec/videodec2.h @@ -10,7 +10,7 @@ namespace Core::Loader { class SymbolsResolver; } -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { class VdecDecoder; @@ -138,4 +138,4 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp void* p1stPictureInfo, void* p2ndPictureInfo); void RegisterLib(Core::Loader::SymbolsResolver* sym); -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_avc.h b/src/core/libraries/videodec/videodec2_avc.h index 725d2335f..dcf6ae007 100644 --- a/src/core/libraries/videodec/videodec2_avc.h +++ b/src/core/libraries/videodec/videodec2_avc.h @@ -5,7 +5,7 @@ #include "common/types.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { struct OrbisVideodec2AvcPictureInfo { u64 thisSize; @@ -127,4 +127,4 @@ struct OrbisVideodec2LegacyAvcPictureInfo { }; static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68); -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index 2ad3d7e19..08a7f8f00 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -9,7 +9,7 @@ #include "common/support/avdec.h" -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { std::vector gPictureInfos; std::vector gLegacyPictureInfos; @@ -278,4 +278,4 @@ AVFrame* VdecDecoder::ConvertNV12Frame(AVFrame& frame) { return nv12_frame; } -} // namespace Libraries::Vdec2 +} // namespace Libraries::Videodec2 diff --git a/src/core/libraries/videodec/videodec2_impl.h b/src/core/libraries/videodec/videodec2_impl.h index 7ee3339db..da16b3baa 100644 --- a/src/core/libraries/videodec/videodec2_impl.h +++ b/src/core/libraries/videodec/videodec2_impl.h @@ -13,7 +13,7 @@ extern "C" { #include } -namespace Libraries::Vdec2 { +namespace Libraries::Videodec2 { extern std::vector gPictureInfos; extern std::vector gLegacyPictureInfos; @@ -37,4 +37,4 @@ private: SwsContext* mSwsContext = nullptr; }; -} // namespace Libraries::Vdec2 \ No newline at end of file +} // namespace Libraries::Videodec2 \ No newline at end of file diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 07b7658f4..60f09eaee 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -315,35 +315,47 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 prot |= MemoryProt::CpuRead; } - // Carve out the new VMA representing this mapping - const auto new_vma_handle = CarveVMA(mapped_addr, size); - auto& new_vma = new_vma_handle->second; - new_vma.disallow_merge = false; - new_vma.prot = prot; - new_vma.name = "anon"; - new_vma.type = Core::VMAType::Pooled; - new_vma.is_exec = false; - - // Find a suitable physical address + // Find suitable physical addresses auto handle = dmem_map.begin(); - while (handle != dmem_map.end() && - (handle->second.dma_type != Core::DMAType::Pooled || handle->second.size < size)) { + u64 remaining_size = size; + VAddr current_addr = mapped_addr; + while (handle != dmem_map.end() && remaining_size != 0) { + if (handle->second.dma_type != DMAType::Pooled) { + // Non-pooled means it's either not for pool use, or already committed. + handle++; + continue; + } + + // On PS4, commits can make sparse physical mappings. + // For now, it's easier to create separate memory mappings for each physical mapping. + u64 size_to_map = std::min(remaining_size, handle->second.size); + + // Carve out the new VMA representing this mapping + const auto new_vma_handle = CarveVMA(current_addr, size_to_map); + auto& new_vma = new_vma_handle->second; + new_vma.disallow_merge = false; + new_vma.prot = prot; + new_vma.name = "anon"; + new_vma.type = Core::VMAType::Pooled; + new_vma.is_exec = false; + + // Use the start of this area as the physical backing for this mapping. + const auto new_dmem_handle = CarveDmemArea(handle->second.base, size_to_map); + auto& new_dmem_area = new_dmem_handle->second; + new_dmem_area.dma_type = DMAType::Committed; + new_dmem_area.memory_type = mtype; + new_vma.phys_base = new_dmem_area.base; + handle = MergeAdjacent(dmem_map, new_dmem_handle); + + // Perform the mapping + void* out_addr = impl.Map(current_addr, size_to_map, alignment, new_vma.phys_base, false); + TRACK_ALLOC(out_addr, size_to_map, "VMEM"); + + current_addr += size_to_map; + remaining_size -= size_to_map; handle++; } - ASSERT_MSG(handle != dmem_map.end() && handle->second.dma_type == Core::DMAType::Pooled, - "No suitable physical memory areas to map"); - - // Use the start of this area as the physical backing for this mapping. - const auto new_dmem_handle = CarveDmemArea(handle->second.base, size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Committed; - new_dmem_area.memory_type = mtype; - new_vma.phys_base = new_dmem_area.base; - MergeAdjacent(dmem_map, new_dmem_handle); - - // Perform the mapping - void* out_addr = impl.Map(mapped_addr, size, alignment, new_vma.phys_base, false); - TRACK_ALLOC(out_addr, size, "VMEM"); + ASSERT_MSG(remaining_size == 0, "Unable to map physical memory"); if (IsValidGpuMapping(mapped_addr, size)) { rasterizer->MapMemory(mapped_addr, size); @@ -610,57 +622,64 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) { virtual_addr); std::scoped_lock lk{mutex}; - const auto it = FindVMA(virtual_addr); - const auto& vma_base = it->second; - ASSERT_MSG(vma_base.Contains(virtual_addr, size), - "Existing mapping does not contain requested unmap range"); - - const auto vma_base_addr = vma_base.base; - const auto vma_base_size = vma_base.size; - const auto phys_base = vma_base.phys_base; - const bool is_exec = vma_base.is_exec; - const auto start_in_vma = virtual_addr - vma_base_addr; - const auto type = vma_base.type; - - if (type != VMAType::PoolReserved && type != VMAType::Pooled) { - LOG_ERROR(Kernel_Vmm, "Attempting to decommit non-pooled memory!"); - return ORBIS_KERNEL_ERROR_EINVAL; + // Do an initial search to ensure this decommit is valid. + auto it = FindVMA(virtual_addr); + while (it != vma_map.end() && it->second.base + it->second.size <= virtual_addr + size) { + if (it->second.type != VMAType::PoolReserved && it->second.type != VMAType::Pooled) { + LOG_ERROR(Kernel_Vmm, "Attempting to decommit non-pooled memory!"); + return ORBIS_KERNEL_ERROR_EINVAL; + } + it++; } - if (type == VMAType::Pooled) { - // We always map PoolCommitted memory to GPU, so unmap when decomitting. - if (IsValidGpuMapping(virtual_addr, size)) { - rasterizer->UnmapMemory(virtual_addr, size); + // Loop through all vmas in the area, unmap them. + u64 remaining_size = size; + VAddr current_addr = virtual_addr; + while (remaining_size != 0) { + const auto it = FindVMA(current_addr); + const auto& vma_base = it->second; + const bool is_exec = vma_base.is_exec; + const auto start_in_vma = current_addr - vma_base.base; + const auto size_in_vma = std::min(remaining_size, vma_base.size - start_in_vma); + + if (vma_base.type == VMAType::Pooled) { + // We always map PoolCommitted memory to GPU, so unmap when decomitting. + if (IsValidGpuMapping(current_addr, size_in_vma)) { + rasterizer->UnmapMemory(current_addr, size_in_vma); + } + + // Track how much pooled memory is decommitted + pool_budget += size_in_vma; + + // Re-pool the direct memory used by this mapping + const auto unmap_phys_base = vma_base.phys_base + start_in_vma; + const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size_in_vma); + auto& new_dmem_area = new_dmem_handle->second; + new_dmem_area.dma_type = DMAType::Pooled; + + // Coalesce with nearby direct memory areas. + MergeAdjacent(dmem_map, new_dmem_handle); } - // Track how much pooled memory is decommitted - pool_budget += size; + if (vma_base.type != VMAType::PoolReserved) { + // Unmap the memory region. + impl.Unmap(vma_base.base, vma_base.size, start_in_vma, start_in_vma + size_in_vma, + vma_base.phys_base, vma_base.is_exec, true, false); + TRACK_FREE(virtual_addr, "VMEM"); + } - // Re-pool the direct memory used by this mapping - const auto unmap_phys_base = phys_base + start_in_vma; - const auto new_dmem_handle = CarveDmemArea(unmap_phys_base, size); - auto& new_dmem_area = new_dmem_handle->second; - new_dmem_area.dma_type = DMAType::Pooled; + // Mark region as pool reserved and attempt to coalesce it with neighbours. + const auto new_it = CarveVMA(current_addr, size_in_vma); + auto& vma = new_it->second; + vma.type = VMAType::PoolReserved; + vma.prot = MemoryProt::NoAccess; + vma.phys_base = 0; + vma.disallow_merge = false; + vma.name = "anon"; + MergeAdjacent(vma_map, new_it); - // Coalesce with nearby direct memory areas. - MergeAdjacent(dmem_map, new_dmem_handle); - } - - // Mark region as pool reserved and attempt to coalesce it with neighbours. - const auto new_it = CarveVMA(virtual_addr, size); - auto& vma = new_it->second; - vma.type = VMAType::PoolReserved; - vma.prot = MemoryProt::NoAccess; - vma.phys_base = 0; - vma.disallow_merge = false; - vma.name = "anon"; - MergeAdjacent(vma_map, new_it); - - if (type != VMAType::PoolReserved) { - // Unmap the memory region. - impl.Unmap(vma_base_addr, vma_base_size, start_in_vma, start_in_vma + size, phys_base, - is_exec, true, false); - TRACK_FREE(virtual_addr, "VMEM"); + current_addr += size_in_vma; + remaining_size -= size_in_vma; } return ORBIS_OK; diff --git a/src/core/signals.cpp b/src/core/signals.cpp index db6e4b6cc..8df4edea8 100644 --- a/src/core/signals.cpp +++ b/src/core/signals.cpp @@ -33,6 +33,10 @@ static LONG WINAPI SignalHandler(EXCEPTION_POINTERS* pExp) noexcept { case EXCEPTION_ILLEGAL_INSTRUCTION: handled = signals->DispatchIllegalInstruction(pExp); break; + case DBG_PRINTEXCEPTION_C: + case DBG_PRINTEXCEPTION_WIDE_C: + // Used by OutputDebugString functions. + return EXCEPTION_CONTINUE_EXECUTION; default: break; } diff --git a/src/emulator.cpp b/src/emulator.cpp index cfef7643c..1003fa3ec 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -35,6 +35,7 @@ #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/font/font.h" #include "core/libraries/font/fontft.h" +#include "core/libraries/jpeg/jpegenc.h" #include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libs.h" #include "core/libraries/ngs2/ngs2.h" @@ -542,6 +543,10 @@ void Emulator::LoadSystemModules(const std::string& game_serial) { constexpr auto ModulesToLoad = std::to_array( {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterLib}, {"libSceUlt.sprx", nullptr}, + {"libSceRtc.sprx", &Libraries::Rtc::RegisterLib}, + {"libSceJpegDec.sprx", nullptr}, + {"libSceJpegEnc.sprx", &Libraries::JpegEnc::RegisterLib}, + {"libScePngEnc.sprx", nullptr}, {"libSceJson.sprx", nullptr}, {"libSceJson2.sprx", nullptr}, {"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},