Merge branch 'main' into user_and_settings
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled

This commit is contained in:
georgemoralis 2026-01-12 10:08:06 +02:00 committed by GitHub
commit e16dd9ebe4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 820 additions and 382 deletions

View File

@ -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

View File

@ -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 | | | |
</div>
> [!Caution]

View File

@ -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) \

View File

@ -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

View File

@ -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

View File

@ -19,6 +19,17 @@ struct OrbisKernelDirent;
namespace Core::Directories {
class BaseDirectory {
protected:
static inline u32 fileno_pool{10};
static u32 next_fileno() {
return ++fileno_pool;
}
s64 file_offset = 0;
u64 directory_size = 0;
std::vector<u8> dirent_cache_bin{};
public:
explicit BaseDirectory();
@ -28,13 +39,8 @@ public:
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt);
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset);
virtual s64 write(const void* buf, u64 nbytes) {
return ORBIS_KERNEL_ERROR_EBADF;
@ -48,9 +54,7 @@ public:
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 lseek(s64 offset, s32 whence) {
return ORBIS_KERNEL_ERROR_EBADF;
}
virtual s64 lseek(s64 offset, s32 whence);
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) {
return ORBIS_KERNEL_ERROR_EBADF;

View File

@ -15,111 +15,30 @@ std::shared_ptr<BaseDirectory> NormalDirectory::Create(std::string_view guest_di
std::make_shared<NormalDirectory>(guest_directory));
}
NormalDirectory::NormalDirectory(std::string_view guest_directory) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
static s32 fileno = 0;
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
auto& dirent = dirents.emplace_back();
dirent.d_fileno = ++fileno;
dirent.d_type = (ent_is_file ? 8 : 4);
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
dirent.d_namlen = ent_path.filename().string().size();
// Calculate the appropriate length for this dirent.
// Account for the null terminator in d_name too.
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
(dirent.d_namlen + 1),
4);
directory_size += dirent.d_reclen;
});
// The last entry of a normal directory should have d_reclen covering the remaining data.
// Since the dirents of a folder are constant by this point, we can modify the last dirent
// before creating the emulated file buffer.
const u64 filler_count = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT) - directory_size;
dirents[dirents.size() - 1].d_reclen += filler_count;
// Reading from standard directories seems to be based around file pointer logic.
// Keep an internal buffer representing the raw contents of this file descriptor,
// then emulate the various read functions with that.
directory_size = Common::AlignUp(directory_size, DIRECTORY_ALIGNMENT);
data_buffer.reserve(directory_size);
memset(data_buffer.data(), 0, directory_size);
u8* current_dirent = data_buffer.data();
for (const NormalDirectoryDirent& dirent : dirents) {
NormalDirectoryDirent* dirent_to_write =
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
dirent_to_write->d_fileno = dirent.d_fileno;
// Using size d_namlen + 1 to account for null terminator.
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
dirent_to_write->d_namlen = dirent.d_namlen;
dirent_to_write->d_reclen = dirent.d_reclen;
dirent_to_write->d_type = dirent.d_type;
current_dirent += dirent.d_reclen;
}
NormalDirectory::NormalDirectory(std::string_view guest_directory)
: guest_directory(guest_directory) {
RebuildDirents();
}
s64 NormalDirectory::read(void* buf, u64 nbytes) {
// Nothing left to read.
if (file_offset >= directory_size) {
return ORBIS_OK;
}
RebuildDirents();
const s64 remaining_data = directory_size - file_offset;
const s64 bytes = nbytes > remaining_data ? remaining_data : nbytes;
// data is contiguous. read goes like any regular file would: start at offset, read n bytes
// output is always aligned up to 512 bytes with 0s
// offset - classic. however at the end of read any unused (exceeding dirent buffer size) buffer
// space will be left untouched
// reclen always sums up to end of current alignment
std::memcpy(buf, data_buffer.data() + file_offset, bytes);
s64 bytes_available = this->dirent_cache_bin.size() - file_offset;
if (bytes_available <= 0)
return 0;
bytes_available = std::min<s64>(bytes_available, static_cast<s64>(nbytes));
file_offset += bytes;
return bytes;
}
// data
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
s64 NormalDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
s64 bytes_read = 0;
for (s32 i = 0; i < iovcnt; i++) {
const s64 result = read(iov[i].iov_base, iov[i].iov_len);
if (result < 0) {
return result;
}
bytes_read += result;
}
return bytes_read;
}
s64 NormalDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
s64 offset) {
const u64 old_file_pointer = file_offset;
file_offset = offset;
const s64 bytes_read = readv(iov, iovcnt);
file_offset = old_file_pointer;
return bytes_read;
}
s64 NormalDirectory::lseek(s64 offset, s32 whence) {
switch (whence) {
case 0: {
file_offset = offset;
break;
}
case 1: {
file_offset += offset;
break;
}
case 2: {
file_offset = directory_size + offset;
break;
}
default: {
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
}
}
return file_offset;
file_offset += bytes_available;
return bytes_available;
}
s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
@ -131,10 +50,110 @@ s32 NormalDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
}
s64 NormalDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
if (basep != nullptr) {
RebuildDirents();
if (basep)
*basep = file_offset;
// same as others, we just don't need a variable
if (file_offset >= directory_size)
return 0;
s64 bytes_written = 0;
s64 working_offset = file_offset;
s64 dirent_buffer_offset = 0;
s64 aligned_count = Common::AlignDown(nbytes, 512);
const u8* dirent_buffer = this->dirent_cache_bin.data();
while (dirent_buffer_offset < this->dirent_cache_bin.size()) {
const u8* normal_dirent_ptr = dirent_buffer + dirent_buffer_offset;
const NormalDirectoryDirent* normal_dirent =
reinterpret_cast<const NormalDirectoryDirent*>(normal_dirent_ptr);
auto d_reclen = normal_dirent->d_reclen;
// bad, incomplete or OOB entry
if (normal_dirent->d_namlen == 0)
break;
if (working_offset >= d_reclen) {
dirent_buffer_offset += d_reclen;
working_offset -= d_reclen;
continue;
}
if ((bytes_written + d_reclen) > aligned_count)
// dirents are aligned to the last full one
break;
memcpy(static_cast<u8*>(buf) + bytes_written, normal_dirent_ptr + working_offset,
d_reclen - working_offset);
bytes_written += d_reclen - working_offset;
dirent_buffer_offset += d_reclen;
working_offset = 0;
}
// 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<Core::FileSys::MntPoints>::Instance();
mnt->IterateDirectory(
guest_directory, [this, &next_ceiling, &dirent_offset, &last_reclen_offset](
const std::filesystem::path& ent_path, const bool ent_is_file) {
NormalDirectoryDirent tmp{};
std::string leaf(ent_path.filename().string());
// prepare dirent
tmp.d_fileno = BaseDirectory::next_fileno();
tmp.d_namlen = leaf.size();
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
tmp.d_type = (ent_is_file ? 0100000 : 0040000) >> 12;
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 4);
// next element may break 512 byte alignment
if (tmp.d_reclen + dirent_offset > next_ceiling) {
// align previous dirent's size to the current ceiling
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) +
last_reclen_offset) += next_ceiling - dirent_offset;
// set writing pointer to the aligned start position (current ceiling)
dirent_offset = next_ceiling;
// move the ceiling up and zero-out the buffer
next_ceiling += 512;
dirent_cache_bin.resize(next_ceiling);
std::fill(dirent_cache_bin.begin() + dirent_offset,
dirent_cache_bin.begin() + next_ceiling, 0);
}
// current dirent's reclen position
last_reclen_offset = dirent_offset + 4;
memcpy(dirent_cache_bin.data() + dirent_offset, &tmp, tmp.d_reclen);
dirent_offset += tmp.d_reclen;
});
// last reclen, as before
*reinterpret_cast<u16*>(static_cast<u8*>(dirent_cache_bin.data()) + last_reclen_offset) +=
next_ceiling - dirent_offset;
// i have no idea if this is the case, but lseek returns size aligned to 512
directory_size = next_ceiling;
}
} // namespace Core::Directories

View File

@ -19,27 +19,23 @@ public:
~NormalDirectory() override = default;
virtual s64 read(void* buf, u64 nbytes) override;
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
s64 offset) override;
virtual s64 lseek(s64 offset, s32 whence) override;
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
private:
static constexpr s32 MAX_LENGTH = 255;
static constexpr s64 DIRECTORY_ALIGNMENT = 0x200;
#pragma pack(push, 1)
struct NormalDirectoryDirent {
u32 d_fileno;
u16 d_reclen;
u8 d_type;
u8 d_namlen;
char d_name[MAX_LENGTH + 1];
char d_name[256];
};
#pragma pack(pop)
u64 directory_size = 0;
s64 file_offset = 0;
std::vector<u8> data_buffer;
std::vector<NormalDirectoryDirent> dirents;
std::string_view guest_directory{};
s64 previous_file_offset = -1;
void RebuildDirents(void);
};
} // namespace Core::Directories

View File

@ -15,77 +15,49 @@ std::shared_ptr<BaseDirectory> PfsDirectory::Create(std::string_view guest_direc
}
PfsDirectory::PfsDirectory(std::string_view guest_directory) {
constexpr u32 dirent_meta_size =
sizeof(PfsDirectoryDirent::d_fileno) + sizeof(PfsDirectoryDirent::d_type) +
sizeof(PfsDirectoryDirent::d_namlen) + sizeof(PfsDirectoryDirent::d_reclen);
dirent_cache_bin.reserve(512);
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
static s32 fileno = 0;
mnt->IterateDirectory(guest_directory, [this](const auto& ent_path, const auto ent_is_file) {
auto& dirent = dirents.emplace_back();
dirent.d_fileno = ++fileno;
dirent.d_type = (ent_is_file ? 8 : 4);
strncpy(dirent.d_name, ent_path.filename().string().data(), MAX_LENGTH + 1);
dirent.d_namlen = ent_path.filename().string().size();
mnt->IterateDirectory(
guest_directory, [this](const std::filesystem::path& ent_path, const bool ent_is_file) {
PfsDirectoryDirent tmp{};
std::string leaf(ent_path.filename().string());
// Calculate the appropriate length for this dirent.
// Account for the null terminator in d_name too.
dirent.d_reclen = Common::AlignUp(sizeof(dirent.d_fileno) + sizeof(dirent.d_type) +
sizeof(dirent.d_namlen) + sizeof(dirent.d_reclen) +
(dirent.d_namlen + 1),
8);
tmp.d_fileno = BaseDirectory::next_fileno();
tmp.d_namlen = leaf.size();
strncpy(tmp.d_name, leaf.data(), tmp.d_namlen + 1);
tmp.d_type = ent_is_file ? 2 : 4;
tmp.d_reclen = Common::AlignUp(dirent_meta_size + tmp.d_namlen + 1, 8);
auto dirent_ptr = reinterpret_cast<const u8*>(&tmp);
// To handle some obscure dirents_index behavior,
// keep track of the "actual" length of this directory.
directory_content_size += dirent.d_reclen;
});
dirent_cache_bin.insert(dirent_cache_bin.end(), dirent_ptr, dirent_ptr + tmp.d_reclen);
});
directory_size = Common::AlignUp(directory_content_size, DIRECTORY_ALIGNMENT);
directory_size = Common::AlignUp(dirent_cache_bin.size(), 0x10000);
}
s64 PfsDirectory::read(void* buf, u64 nbytes) {
if (dirents_index >= dirents.size()) {
if (dirents_index < directory_content_size) {
// We need to find the appropriate dirents_index to start from.
s64 data_to_skip = dirents_index;
u64 corrected_index = 0;
while (data_to_skip > 0) {
const auto dirent = dirents[corrected_index++];
data_to_skip -= dirent.d_reclen;
}
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<s64>(bytes_available, static_cast<s64>(nbytes));
memcpy(buf, this->dirent_cache_bin.data() + file_offset, bytes_available);
s64 to_fill =
(std::min<s64>(directory_size, static_cast<s64>(nbytes))) - bytes_available - file_offset;
if (to_fill < 0) {
LOG_ERROR(Kernel_Fs, "Dirent may have leaked {} bytes", -to_fill);
return -to_fill + bytes_available;
}
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
// read on PfsDirectories will always return the maximum possible value.
const u64 bytes_written = bytes_remaining;
memset(buf, 0, bytes_remaining);
char* current_dirent = static_cast<char*>(buf);
PfsDirectoryDirent dirent = dirents[dirents_index];
while (bytes_remaining > dirent.d_reclen) {
PfsDirectoryDirent* dirent_to_write = reinterpret_cast<PfsDirectoryDirent*>(current_dirent);
dirent_to_write->d_fileno = dirent.d_fileno;
// Using size d_namlen + 1 to account for null terminator.
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
dirent_to_write->d_namlen = dirent.d_namlen;
dirent_to_write->d_reclen = dirent.d_reclen;
dirent_to_write->d_type = dirent.d_type;
current_dirent += dirent.d_reclen;
bytes_remaining -= dirent.d_reclen;
if (dirents_index == dirents.size() - 1) {
// Currently at the last dirent, so break out of the loop.
dirents_index++;
break;
}
dirent = dirents[++dirents_index];
}
return bytes_written;
memset(static_cast<u8*>(buf) + bytes_available, 0, to_fill);
file_offset += to_fill + bytes_available;
return to_fill + bytes_available;
}
s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) {
@ -101,62 +73,13 @@ s64 PfsDirectory::readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovc
}
s64 PfsDirectory::preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt, s64 offset) {
const u64 old_dirent_index = dirents_index;
dirents_index = 0;
s64 data_to_skip = offset;
// If offset is part-way through one dirent, that dirent is skipped.
while (data_to_skip > 0) {
const auto dirent = dirents[dirents_index++];
data_to_skip -= dirent.d_reclen;
if (dirents_index == dirents.size()) {
// We've reached the end of the dirents, nothing more can be skipped.
break;
}
}
const u64 old_file_pointer = file_offset;
file_offset = offset;
const s64 bytes_read = readv(iov, iovcnt);
dirents_index = old_dirent_index;
file_offset = old_file_pointer;
return bytes_read;
}
s64 PfsDirectory::lseek(s64 offset, s32 whence) {
switch (whence) {
// Seek start
case 0: {
dirents_index = 0;
}
case 1: {
// There aren't any dirents left to pass through.
if (dirents_index >= dirents.size()) {
dirents_index = dirents_index + offset;
break;
}
s64 data_to_skip = offset;
while (data_to_skip > 0) {
const auto dirent = dirents[dirents_index++];
data_to_skip -= dirent.d_reclen;
if (dirents_index == dirents.size()) {
// We've passed through all file dirents.
// Set dirents_index to directory_size + remaining_offset instead.
dirents_index = directory_content_size + data_to_skip;
break;
}
}
break;
}
case 2: {
// Seems like real hardware gives up on tracking dirents_index if you go this route.
dirents_index = directory_size + offset;
break;
}
default: {
UNREACHABLE_MSG("lseek with unknown whence {}", whence);
}
}
return dirents_index;
}
s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
stat->st_mode = 0000777u | 0040000u;
stat->st_size = directory_size;
@ -166,55 +89,58 @@ s32 PfsDirectory::fstat(Libraries::Kernel::OrbisKernelStat* stat) {
}
s64 PfsDirectory::getdents(void* buf, u64 nbytes, s64* basep) {
// basep is set at the start of the function.
if (basep != nullptr) {
*basep = dirents_index;
}
if (basep)
*basep = file_offset;
if (dirents_index >= dirents.size()) {
if (dirents_index < directory_content_size) {
// We need to find the appropriate dirents_index to start from.
s64 data_to_skip = dirents_index;
u64 corrected_index = 0;
while (data_to_skip > 0) {
const auto dirent = dirents[corrected_index++];
data_to_skip -= dirent.d_reclen;
}
dirents_index = corrected_index;
} else {
// Nothing left to read.
return ORBIS_OK;
}
}
s64 bytes_remaining = nbytes > directory_size ? directory_size : nbytes;
memset(buf, 0, bytes_remaining);
// same as others, we just don't need a variable
if (file_offset >= directory_size)
return 0;
u64 bytes_written = 0;
char* current_dirent = static_cast<char*>(buf);
// getdents has to convert pfs dirents to normal dirents
PfsDirectoryDirent dirent = dirents[dirents_index];
while (bytes_remaining > dirent.d_reclen) {
NormalDirectoryDirent* dirent_to_write =
reinterpret_cast<NormalDirectoryDirent*>(current_dirent);
dirent_to_write->d_fileno = dirent.d_fileno;
strncpy(dirent_to_write->d_name, dirent.d_name, dirent.d_namlen + 1);
dirent_to_write->d_namlen = dirent.d_namlen;
dirent_to_write->d_reclen = dirent.d_reclen;
dirent_to_write->d_type = dirent.d_type;
u64 starting_offset = 0;
u64 buffer_position = 0;
while (buffer_position < this->dirent_cache_bin.size()) {
const PfsDirectoryDirent* pfs_dirent =
reinterpret_cast<PfsDirectoryDirent*>(this->dirent_cache_bin.data() + buffer_position);
current_dirent += dirent.d_reclen;
bytes_remaining -= dirent.d_reclen;
bytes_written += dirent.d_reclen;
if (dirents_index == dirents.size() - 1) {
// Currently at the last dirent, so set dirents_index appropriately and break.
dirents_index = directory_size;
// bad, incomplete or OOB entry
if (pfs_dirent->d_namlen == 0)
break;
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<u8*>(buf) + bytes_written, &normal_dirent, normal_dirent.d_reclen);
bytes_written += normal_dirent.d_reclen;
buffer_position = bytes_written + starting_offset;
}
file_offset = (buffer_position >= this->dirent_cache_bin.size())
? directory_size
: (file_offset + bytes_written);
return bytes_written;
}
} // namespace Core::Directories

View File

@ -22,32 +22,28 @@ public:
virtual s64 readv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt) override;
virtual s64 preadv(const Libraries::Kernel::OrbisKernelIovec* iov, s32 iovcnt,
s64 offset) override;
virtual s64 lseek(s64 offset, s32 whence) override;
virtual s32 fstat(Libraries::Kernel::OrbisKernelStat* stat) override;
virtual s64 getdents(void* buf, u64 nbytes, s64* basep) override;
private:
static constexpr s32 MAX_LENGTH = 255;
static constexpr s32 DIRECTORY_ALIGNMENT = 0x10000;
#pragma pack(push, 1)
struct PfsDirectoryDirent {
u32 d_fileno;
u32 d_type;
u32 d_namlen;
u32 d_reclen;
char d_name[MAX_LENGTH + 1];
char d_name[256];
};
#pragma pack(pop)
#pragma pack(push, 1)
struct NormalDirectoryDirent {
u32 d_fileno;
u16 d_reclen;
u8 d_type;
u8 d_namlen;
char d_name[MAX_LENGTH + 1];
char d_name[256];
};
u64 directory_size = 0;
u64 directory_content_size = 0;
s64 dirents_index = 0;
std::vector<PfsDirectoryDirent> dirents;
#pragma pack(pop)
};
} // namespace Core::Directories

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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
} // namespace Libraries::Videodec2

View File

@ -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
} // namespace Libraries::Videodec2

View File

@ -9,7 +9,7 @@
#include "common/support/avdec.h"
namespace Libraries::Vdec2 {
namespace Libraries::Videodec2 {
std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
@ -278,4 +278,4 @@ AVFrame* VdecDecoder::ConvertNV12Frame(AVFrame& frame) {
return nv12_frame;
}
} // namespace Libraries::Vdec2
} // namespace Libraries::Videodec2

View File

@ -13,7 +13,7 @@ extern "C" {
#include <libswscale/swscale.h>
}
namespace Libraries::Vdec2 {
namespace Libraries::Videodec2 {
extern std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
extern std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
@ -37,4 +37,4 @@ private:
SwsContext* mSwsContext = nullptr;
};
} // namespace Libraries::Vdec2
} // namespace Libraries::Videodec2

View File

@ -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<u64>(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<u64>(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;

View File

@ -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;
}

View File

@ -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<SysModules>(
{{"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},