merge main

This commit is contained in:
Matthew Biskas 2026-01-13 12:34:37 +01:00
commit 3f73aedfde
37 changed files with 940 additions and 436 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
@ -840,6 +842,8 @@ set(CORE src/core/aerolib/stubs.cpp
src/core/thread.h
src/core/tls.cpp
src/core/tls.h
src/core/emulator_state.cpp
src/core/emulator_state.h
)
if (ARCHITECTURE STREQUAL "x86_64")

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

@ -198,7 +198,6 @@ static ConfigEntry<bool> pipelineCacheArchive(false);
static ConfigEntry<bool> isDebugDump(false);
static ConfigEntry<bool> isShaderDebug(false);
static ConfigEntry<bool> isSeparateLogFilesEnabled(false);
static ConfigEntry<bool> isFpsColor(true);
static ConfigEntry<bool> showFpsCounter(false);
static ConfigEntry<bool> logEnabled(true);
@ -223,16 +222,6 @@ static string config_version = Common::g_scm_rev;
// These entries aren't stored in the config
static bool overrideControllerColor = false;
static int controllerCustomColorRGB[3] = {0, 0, 255};
static bool isGameRunning = false;
static bool load_auto_patches = true;
bool getGameRunning() {
return isGameRunning;
}
void setGameRunning(bool running) {
isGameRunning = running;
}
std::filesystem::path getSysModulesPath() {
if (sys_modules_path.empty()) {
@ -463,10 +452,6 @@ bool isPipelineCacheArchived() {
return pipelineCacheArchive.get();
}
bool fpsColor() {
return isFpsColor.get();
}
bool getShowFpsCounter() {
return showFpsCounter.get();
}
@ -855,13 +840,6 @@ void setUsbDeviceBackend(int value, bool is_game_specific) {
usbDeviceBackend.set(value, is_game_specific);
}
bool getLoadAutoPatches() {
return load_auto_patches;
}
void setLoadAutoPatches(bool enable) {
load_auto_patches = enable;
}
void load(const std::filesystem::path& path, bool is_game_specific) {
// If the configuration file does not exist, create it and return, unless it is game specific
std::error_code error;
@ -977,7 +955,6 @@ void load(const std::filesystem::path& path, bool is_game_specific) {
isDebugDump.setFromToml(debug, "DebugDump", is_game_specific);
isSeparateLogFilesEnabled.setFromToml(debug, "isSeparateLogFilesEnabled", is_game_specific);
isShaderDebug.setFromToml(debug, "CollectShader", is_game_specific);
isFpsColor.setFromToml(debug, "FPSColor", is_game_specific);
showFpsCounter.setFromToml(debug, "showFpsCounter", is_game_specific);
logEnabled.setFromToml(debug, "logEnabled", is_game_specific);
current_version = toml::find_or<std::string>(debug, "ConfigVersion", current_version);
@ -1197,7 +1174,6 @@ void save(const std::filesystem::path& path, bool is_game_specific) {
data["GPU"]["internalScreenWidth"] = internalScreenWidth.base_value;
data["GPU"]["internalScreenHeight"] = internalScreenHeight.base_value;
data["GPU"]["patchShaders"] = shouldPatchShaders.base_value;
data["Debug"]["FPSColor"] = isFpsColor.base_value;
data["Debug"]["showFpsCounter"] = showFpsCounter.base_value;
}
@ -1306,7 +1282,6 @@ void setDefaultValues(bool is_game_specific) {
internalScreenHeight.base_value = 720;
// Debug
isFpsColor.base_value = true;
showFpsCounter.base_value = false;
}
}

View File

@ -125,7 +125,6 @@ int getSpecialPadClass();
bool getPSNSignedIn();
void setPSNSignedIn(bool sign, bool is_game_specific = false);
bool patchShaders(); // no set
bool fpsColor(); // no set
bool getShowFpsCounter();
void setShowFpsCounter(bool enable, bool is_game_specific = false);
bool isNeoModeConsole();
@ -154,8 +153,6 @@ void setConnectedToNetwork(bool enable, bool is_game_specific = false);
void setUserName(const std::string& name, bool is_game_specific = false);
std::filesystem::path getSysModulesPath();
void setSysModulesPath(const std::filesystem::path& path);
bool getLoadAutoPatches();
void setLoadAutoPatches(bool enable);
enum UsbBackendType : int { Real, SkylandersPortal, InfinityBase, DimensionsToypad };
int getUsbDeviceBackend();

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

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
@ -12,6 +12,7 @@
#include "common/elf_info.h"
#include "common/logging/log.h"
#include "common/path_util.h"
#include "core/emulator_state.h"
#include "core/file_format/psf.h"
#include "memory_patcher.h"
@ -192,7 +193,7 @@ void OnGameLoaded() {
} else {
ApplyPatchesFromXML(file_path);
}
} else if (Config::getLoadAutoPatches()) {
} else if (EmulatorState::GetInstance()->IsAutoPatchesLoadEnabled()) {
for (auto const& repo : std::filesystem::directory_iterator(patch_dir)) {
if (!repo.is_directory()) {
continue;

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "layer.h"
@ -11,6 +11,7 @@
#include "common/singleton.h"
#include "common/types.h"
#include "core/debug_state.h"
#include "core/emulator_state.h"
#include "imgui/imgui_std.h"
#include "imgui_internal.h"
#include "options.h"
@ -273,14 +274,10 @@ void L::DrawAdvanced() {
void L::DrawSimple() {
const float frameRate = DebugState.Framerate;
if (Config::fpsColor()) {
if (frameRate < 10) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red
} else if (frameRate >= 10 && frameRate < 20) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange
} else {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White
}
if (frameRate < 10) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); // Red
} else if (frameRate >= 10 && frameRate < 20) {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.5f, 0.0f, 1.0f)); // Orange
} else {
PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); // White
}

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "emulator_state.h"
std::shared_ptr<EmulatorState> EmulatorState::s_instance = nullptr;
std::mutex EmulatorState::s_mutex;
EmulatorState::EmulatorState() {}
EmulatorState::~EmulatorState() {}
std::shared_ptr<EmulatorState> EmulatorState::GetInstance() {
std::lock_guard<std::mutex> lock(s_mutex);
if (!s_instance)
s_instance = std::make_shared<EmulatorState>();
return s_instance;
}
void EmulatorState::SetInstance(std::shared_ptr<EmulatorState> instance) {
std::lock_guard<std::mutex> lock(s_mutex);
s_instance = instance;
}
bool EmulatorState::IsGameRunning() const {
return m_running;
}
void EmulatorState::SetGameRunning(bool running) {
m_running = running;
}
bool EmulatorState::IsAutoPatchesLoadEnabled() const {
return m_load_patches_auto;
}
void EmulatorState::SetAutoPatchesLoadEnabled(bool enable) {
m_load_patches_auto = enable;
}

29
src/core/emulator_state.h Normal file
View File

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2025-2026 shadLauncher4 Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <mutex>
class EmulatorState {
public:
EmulatorState();
~EmulatorState();
static std::shared_ptr<EmulatorState> GetInstance();
static void SetInstance(std::shared_ptr<EmulatorState> instance);
bool IsGameRunning() const;
void SetGameRunning(bool running);
bool IsAutoPatchesLoadEnabled() const;
void SetAutoPatchesLoadEnabled(bool enable);
private:
static std::shared_ptr<EmulatorState> s_instance;
static std::mutex s_mutex;
// state variables
bool m_running = false;
bool m_load_patches_auto = true;
};

View File

@ -59,7 +59,7 @@ static void hexToBytes(const char* hex, unsigned char* dst) {
bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
if (!std::filesystem::exists(gameSysDir)) {
LOG_CRITICAL(Common_Filesystem, "Game sce_sys directory doesn't exist");
LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist");
return false;
}

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

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "ipc.h"
@ -14,6 +14,7 @@
#include "common/types.h"
#include "core/debug_state.h"
#include "core/debugger.h"
#include "core/emulator_state.h"
#include "core/libraries/audio/audioout.h"
#include "input/input_handler.h"
#include "sdl_window.h"
@ -71,7 +72,7 @@ void IPC::Init() {
return;
}
Config::setLoadAutoPatches(false);
EmulatorState::GetInstance()->SetAutoPatchesLoadEnabled(false);
input_thread = std::jthread([this] {
Common::SetCurrentThreadName("IPC Read thread");

View File

@ -280,9 +280,7 @@ AjmJob AjmJobFromBatchBuffer(u32 instance_id, AjmBatchBuffer batch_buffer) {
job.input.resample_parameters = input_batch.Consume<AjmSidebandResampleParameters>();
}
if (True(control_flags & AjmJobControlFlags::Initialize)) {
job.input.init_params = AjmDecAt9InitializeParameters{};
std::memcpy(&job.input.init_params.value(), input_batch.GetCurrent(),
input_batch.BytesRemaining());
job.input.init_params = input_batch.Consume<AjmSidebandInitParameters>();
}
}

View File

@ -21,7 +21,7 @@ namespace Libraries::Ajm {
struct AjmJob {
struct Input {
std::optional<AjmDecAt9InitializeParameters> init_params;
std::optional<AjmSidebandInitParameters> init_params;
std::optional<AjmSidebandResampleParameters> resample_parameters;
std::optional<AjmSidebandStatisticsEngineParameters> statistics_engine_parameters;
std::optional<AjmSidebandFormat> format;

View File

@ -189,7 +189,7 @@ s32 PS4_SYSV_ABI sceAudio3dDeleteSpeakerArray() {
s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters* params) {
LOG_DEBUG(Lib_Audio3d, "called");
if (params) {
*params = OrbisAudio3dOpenParameters{
auto default_params = OrbisAudio3dOpenParameters{
.size_this = 0x20,
.granularity = 0x100,
.rate = OrbisAudio3dRate::ORBIS_AUDIO3D_RATE_48000,
@ -197,6 +197,7 @@ s32 PS4_SYSV_ABI sceAudio3dGetDefaultOpenParameters(OrbisAudio3dOpenParameters*
.queue_depth = 2,
.buffer_mode = OrbisAudio3dBufferMode::ORBIS_AUDIO3D_BUFFER_ADVANCE_AND_PUSH,
};
memcpy(params, &default_params, 0x20);
}
return ORBIS_OK;
}
@ -445,7 +446,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
}
*port_id = id;
std::memcpy(&state->ports[id].parameters, parameters, sizeof(OrbisAudio3dOpenParameters));
std::memcpy(&state->ports[id].parameters, parameters, parameters->size_this);
return ORBIS_OK;
}

View File

@ -36,9 +36,11 @@ s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
}
s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) {
s32 version = Common::ElfInfo::Instance().CompiledSdkVer();
*ver = version;
return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
if (!ver) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
*ver = Common::ElfInfo::Instance().CompiledSdkVer();
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelGetCpumode() {

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

@ -180,8 +180,8 @@ s32 Linker::LoadAndStartModule(const std::filesystem::path& path, u64 args, cons
}
// Retrieve and verify proc param according to libkernel.
u64* param = module->GetProcParam<u64*>();
ASSERT_MSG(!param || param[0] >= 0x18, "Invalid module param size: {}", param[0]);
auto* param = module->GetProcParam<OrbisProcParam*>();
ASSERT_MSG(!param || param->size >= 0x18, "Invalid module param size: {}", param->size);
s32 ret = module->Start(args, argp, param);
if (pRes) {
*pRes = ret;

View File

@ -314,35 +314,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);
@ -609,57 +621,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

@ -34,6 +34,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"
@ -74,6 +75,25 @@ Emulator::Emulator() {
Emulator::~Emulator() {}
s32 ReadCompiledSdkVersion(const std::filesystem::path& file) {
Core::Loader::Elf elf;
elf.Open(file);
if (!elf.IsElfFile()) {
return 0;
}
const auto elf_pheader = elf.GetProgramHeader();
auto i_procparam = std::find_if(elf_pheader.begin(), elf_pheader.end(), [](const auto& entry) {
return entry.p_type == PT_SCE_PROCPARAM;
});
if (i_procparam != elf_pheader.end()) {
Core::OrbisProcParam param{};
elf.LoadSegment(u64(&param), i_procparam->p_offset, i_procparam->p_filesz);
return param.sdk_version;
}
return 0;
}
void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
std::optional<std::filesystem::path> p_game_folder) {
Common::SetCurrentThreadName("Main Thread");
@ -162,6 +182,9 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
auto guest_eboot_path = "/app0/" + eboot_name.generic_string();
const auto eboot_path = mnt->GetHostPath(guest_eboot_path);
auto& game_info = Common::ElfInfo::Instance();
game_info.initialized = true;
game_info.game_serial = id;
@ -169,7 +192,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
game_info.app_ver = app_version;
game_info.firmware_ver = fw_version & 0xFFF00000;
game_info.raw_firmware_ver = fw_version;
game_info.sdk_ver = sdk_version;
game_info.sdk_ver = ReadCompiledSdkVersion(eboot_path);
game_info.psf_attributes = psf_attributes;
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
@ -241,7 +264,8 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
if (param_sfo_exists) {
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
LOG_INFO(Loader, "Compiled SDK version: {:#x}", sdk_version);
LOG_INFO(Loader, "param.sfo SDK version: {:#x}", sdk_version);
LOG_INFO(Loader, "eboot SDK version: {:#x}", game_info.sdk_ver);
LOG_INFO(Loader, "PSVR Supported: {}", (bool)psf_attributes.support_ps_vr.Value());
LOG_INFO(Loader, "PSVR Required: {}", (bool)psf_attributes.require_ps_vr.Value());
}
@ -339,8 +363,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
Libraries::InitHLELibs(&linker->GetHLESymbols());
// Load the module with the linker
auto guest_eboot_path = "/app0/" + eboot_name.generic_string();
const auto eboot_path = mnt->GetHostPath(guest_eboot_path);
if (linker->LoadModule(eboot_path) == -1) {
LOG_CRITICAL(Loader, "Failed to load game's eboot.bin: {}",
Common::FS::PathToUTF8String(std::filesystem::absolute(eboot_path)));
@ -502,6 +524,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},

View File

@ -70,7 +70,7 @@ void EmulateJoystick(GameController* controller, u32 interval) {
SDL_GetRelativeMouseState(&d_x, &d_y);
float output_speed =
SDL_clamp((sqrt(d_x * d_x + d_y * d_y) + mouse_speed_offset * 128) * mouse_speed,
SDL_clamp(sqrt(d_x * d_x + d_y * d_y) * mouse_speed + mouse_speed_offset * 128,
mouse_deadzone_offset * 128, 128.0);
float angle = atan2(d_y, d_x);

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL3/SDL_messagebox.h>
@ -8,6 +8,7 @@
#include "system_error"
#include "unordered_map"
#include <core/emulator_state.h>
#include <fmt/core.h>
#include "common/config.h"
#include "common/logging/backend.h"
@ -27,7 +28,9 @@ int main(int argc, char* argv[]) {
SetConsoleOutputCP(CP_UTF8);
#endif
IPC::Instance().Init();
// Init emulator state
std::shared_ptr<EmulatorState> m_emu_state = std::make_shared<EmulatorState>();
EmulatorState::SetInstance(m_emu_state);
// Load configurations
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::load(user_dir / "config.toml");