Merge branch 'main' into ime-fixes-again

This commit is contained in:
Valdis Bogdāns 2026-01-29 20:07:08 +02:00 committed by GitHub
commit 1016a6faa7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 1747 additions and 105 deletions

View File

@ -587,6 +587,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_score.h
src/core/libraries/np/np_trophy.cpp
src/core/libraries/np/np_trophy.h
src/core/libraries/np/np_tus.cpp
src/core/libraries/np/np_tus.h
src/core/libraries/np/trophy_ui.cpp
src/core/libraries/np/trophy_ui.h
src/core/libraries/np/np_web_api.cpp
@ -601,6 +603,8 @@ set(NP_LIBS src/core/libraries/np/np_error.h
src/core/libraries/np/np_profile_dialog.h
src/core/libraries/np/np_sns_facebook_dialog.cpp
src/core/libraries/np/np_sns_facebook_dialog.h
src/core/libraries/np/np_partner.cpp
src/core/libraries/np/np_partner.h
)
set(ZLIB_LIB src/core/libraries/zlib/zlib.cpp

View File

@ -1,5 +1,5 @@
<!--
SPDX-FileCopyrightText: 2024 shadPS4 Emulator Project
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
@ -58,6 +58,11 @@ This project began for fun. Given our limited free time, it may take some time b
# Building
## Docker
For building shadPS4 in a containerized environment using Docker and VSCode, check the instructions here:
[**Docker Build Instructions**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-docker.md)
## Windows
Check the build instructions for [**Windows**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/building-windows.md).

View File

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
{
"name": "shadPS4-dev",
"dockerComposeFile": [
"../docker-compose.yml"
],
"containerEnv": {
"GITHUB_TOKEN": "${localEnv:GITHUB_TOKEN}",
"GITHUB_USER": "${localEnv:GITHUB_USER}"
},
"service": "shadps4",
"workspaceFolder": "/workspaces/shadPS4",
"remoteUser": "root",
"shutdownAction": "none",
"customizations": {
"vscode": {
"extensions": [
"llvm-vs-code-extensions.vscode-clangd",
"ms-vscode.cmake-tools",
"xaver.clang-format"
],
"settings": {
"clangd.arguments": [
"--background-index",
"--clang-tidy",
"--completion-style=detailed",
"--header-insertion=never",
"--compile-commands-dir=/workspaces/shadPS4/Build/x64-Clang-Release"
],
"C_Cpp.intelliSenseEngine": "Disabled"
}
}
},
"settings": {
"cmake.configureOnOpen": false,
"cmake.generator": "Unix Makefiles",
"cmake.environment": {
"CC": "clang",
"CXX": "clang++"
},
"cmake.configureEnvironment": {
"CMAKE_CXX_STANDARD": "23",
"CMAKE_CXX_STANDARD_REQUIRED": "ON",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
},
"editor.formatOnSave": true,
"clang-format.executable": "clang-format-19"
}
}

View File

@ -0,0 +1,45 @@
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
FROM archlinux:latest
RUN pacman-key --init && \
pacman-key --populate archlinux && \
pacman -Syu --noconfirm
RUN pacman -S --noconfirm \
base-devel \
clang \
clang19 \
ninja \
git \
ca-certificates \
wget \
alsa-lib \
libpulse \
openal \
openssl \
zlib \
libedit \
systemd-libs \
libevdev \
sdl2 \
jack \
sndio \
libxtst \
vulkan-headers \
vulkan-validation-layers \
libpng \
clang-tools-extra \
cmake \
libx11 \
libxrandr \
libxcursor \
libxi \
libxinerama \
libxss \
&& pacman -Scc --noconfirm
RUN ln -sf /usr/lib/llvm19/bin/clang-format /usr/bin/clang-format-19
WORKDIR /workspaces/shadPS4

View File

@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
services:
shadps4:
build:
context: ./.docker
volumes:
- ./emu:/workspaces/shadPS4:cached
tty: true

View File

@ -0,0 +1,100 @@
<!--
SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
SPDX-License-Identifier: GPL-2.0-or-later
-->
# Building shadPS4 with Docker and VSCode Support
This guide explains how to build **shadPS4** using Docker while keeping full compatibility with **VSCode** development.
---
## Prerequisites
Before starting, ensure you have:
- **Docker Engine** or **Docker Desktop** installed
[Installation Guide](https://docs.docker.com/engine/install/)
- **Git** installed on your system.
---
## Step 1: Prepare the Docker Environment
Inside the container (or on your host if mounting volumes):
1. Navigate to the repository folder containing the Docker Builder folder:
```bash
cd <path-to-repo>
```
2. Start the Docker container:
```bash
docker compose up -d
```
This will spin up a container with all the necessary build dependencies, including Clang, CMake, SDL2, Vulkan, and more.
## Step 2: Clone shadPS4 Source
```bash
mkdir emu
cd emu
git clone --recursive https://github.com/shadps4-emu/shadPS4.git .
or your fork link.
```
3. Initialize submodules:
```bash
git submodule update --init --recursive
```
## Step 3: Build with CMake Tools (GUI)
Generate build with CMake Tools.
1. Go `CMake Tools > Configure > '>'`
2. And `Build > '>'`
Compiled executable in `Build` folder.
## Alternative Step 3: Build with CMake
Generate the build directory and configure the project using Clang:
```bash
cmake -S . -B build/ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
```
Then build the project:
```bash
cmake --build ./build --parallel $(nproc)
```
* Tip: To enable debug builds, add -DCMAKE_BUILD_TYPE=Debug to the CMake command.
---
After a successful build, the executable is located at:
```bash
./build/shadps4
```
## Step 4: VSCode Integration
1. Open the repository in VSCode.
2. The CMake Tools extension should automatically detect the build directory inside the container or on your host.
3. You can configure build options, build, and debug directly from the VSCode interface without extra manual setup.
# Notes
* The Docker environment contains all dependencies, so you dont need to install anything manually.
* Using Clang inside Docker ensures consistent builds across Linux and macOS runners.
* GitHub Actions are recommended for cross-platform builds, including Windows .exe output, which is not trivial to produce locally without Visual Studio or clang-cl.

2
externals/MoltenVK vendored

@ -1 +1 @@
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e
Subproject commit f79c6c5690d3ee06ec3a00d11a8b1bab4aa1d030

View File

@ -109,10 +109,12 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, NpManager) \
SUB(Lib, NpScore) \
SUB(Lib, NpTrophy) \
SUB(Lib, NpTus) \
SUB(Lib, NpWebApi) \
SUB(Lib, NpWebApi2) \
SUB(Lib, NpProfileDialog) \
SUB(Lib, NpSnsFacebookDialog) \
SUB(Lib, NpPartner) \
SUB(Lib, Screenshot) \
SUB(Lib, LibCInternal) \
SUB(Lib, AppContent) \

View File

@ -76,6 +76,7 @@ enum class Class : u8 {
Lib_NpManager, ///< The LibSceNpManager implementation
Lib_NpScore, ///< The LibSceNpScore implementation
Lib_NpTrophy, ///< The LibSceNpTrophy implementation
Lib_NpTus, ///< The LibSceNpTus implementation
Lib_NpWebApi, ///< The LibSceWebApi implementation
Lib_NpWebApi2, ///< The LibSceWebApi2 implementation
Lib_NpProfileDialog, ///< The LibSceNpProfileDialog implementation
@ -110,6 +111,7 @@ enum class Class : u8 {
Lib_Mouse, ///< The LibSceMouse implementation
Lib_WebBrowserDialog, ///< The LibSceWebBrowserDialog implementation
Lib_NpParty, ///< The LibSceNpParty implementation
Lib_NpPartner, ///< The LibSceNpPartner implementation
Lib_Zlib, ///< The LibSceZlib implementation.
Lib_Hmd, ///< The LibSceHmd implementation.
Lib_HmdSetupDialog, ///< The LibSceHmdSetupDialog implementation.

View File

@ -17,6 +17,15 @@ public:
writer_active = true;
}
bool try_lock() {
std::lock_guard<std::mutex> lock(mtx);
if (writer_active || readers > 0) {
return false;
}
writer_active = true;
return true;
}
void unlock() {
std::lock_guard<std::mutex> lock(mtx);
writer_active = false;

View File

@ -174,6 +174,9 @@ bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanosec
// Sets the debugger-visible name of the current thread.
void SetCurrentThreadName(const char* name) {
if (Libraries::Kernel::g_curthread) {
Libraries::Kernel::g_curthread->name = std::string{name};
}
SetThreadDescription(GetCurrentThread(), UTF8ToUTF16W(name).data());
}
@ -186,6 +189,9 @@ void SetThreadName(void* thread, const char* name) {
// MinGW with the POSIX threading model does not support pthread_setname_np
#if !defined(_WIN32) || defined(_MSC_VER)
void SetCurrentThreadName(const char* name) {
if (Libraries::Kernel::g_curthread) {
Libraries::Kernel::g_curthread->name = std::string{name};
}
#ifdef __APPLE__
pthread_setname_np(name);
#elif defined(__Bitrig__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__OpenBSD__)
@ -212,6 +218,9 @@ void SetThreadName(void* thread, const char* name) {
#if defined(_WIN32)
void SetCurrentThreadName(const char*) {
if (Libraries::Kernel::g_curthread) {
Libraries::Kernel::g_curthread->name = std::string{name};
}
// Do Nothing on MinGW
}

View File

@ -709,7 +709,7 @@ struct AddressSpace::Impl {
return ret;
}
void Unmap(VAddr virtual_addr, u64 size, bool) {
void Unmap(VAddr virtual_addr, u64 size) {
// Check to see if we are adjacent to any regions.
VAddr start_address = virtual_addr;
VAddr end_address = start_address + size;
@ -792,12 +792,8 @@ void* AddressSpace::MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot,
#endif
}
void AddressSpace::Unmap(VAddr virtual_addr, u64 size, bool has_backing) {
#ifdef _WIN32
void AddressSpace::Unmap(VAddr virtual_addr, u64 size) {
impl->Unmap(virtual_addr, size);
#else
impl->Unmap(virtual_addr, size, has_backing);
#endif
}
void AddressSpace::Protect(VAddr virtual_addr, u64 size, MemoryPermission perms) {

View File

@ -79,8 +79,9 @@ public:
void* MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd);
/// Unmaps specified virtual memory area.
void Unmap(VAddr virtual_addr, u64 size, bool has_backing);
void Unmap(VAddr virtual_addr, u64 size);
/// Protects requested region.
void Protect(VAddr virtual_addr, u64 size, MemoryPermission perms);
// Returns an interval set containing all usable regions.

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 "common/assert.h"
@ -325,6 +325,7 @@ PthreadT PS4_SYSV_ABI posix_pthread_self() {
}
void PS4_SYSV_ABI posix_pthread_set_name_np(PthreadT thread, const char* name) {
LOG_INFO(Kernel_Pthread, "called, new name: {}", name);
Common::SetCurrentThreadName(name);
}

View File

@ -35,11 +35,13 @@
#include "core/libraries/np/np_commerce.h"
#include "core/libraries/np/np_common.h"
#include "core/libraries/np/np_manager.h"
#include "core/libraries/np/np_partner.h"
#include "core/libraries/np/np_party.h"
#include "core/libraries/np/np_profile_dialog.h"
#include "core/libraries/np/np_score.h"
#include "core/libraries/np/np_sns_facebook_dialog.h"
#include "core/libraries/np/np_trophy.h"
#include "core/libraries/np/np_tus.h"
#include "core/libraries/np/np_web_api.h"
#include "core/libraries/np/np_web_api2.h"
#include "core/libraries/pad/pad.h"
@ -105,6 +107,8 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
Libraries::Np::NpSnsFacebookDialog::RegisterLib(sym);
Libraries::Np::NpAuth::RegisterLib(sym);
Libraries::Np::NpParty::RegisterLib(sym);
Libraries::Np::NpPartner::RegisterLib(sym);
Libraries::Np::NpTus::RegisterLib(sym);
Libraries::ScreenShot::RegisterLib(sym);
Libraries::AppContent::RegisterLib(sym);
Libraries::PngDec::RegisterLib(sym);

View File

@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/libraries/np/np_partner.h"
#include "core/libraries/np/np_partner_error.h"
#include "core/libraries/system/userservice.h"
namespace Libraries::Np::NpPartner {
static bool g_library_init = false;
std::mutex g_library_mutex{};
/**
* Terminates the library
*/
s32 PS4_SYSV_ABI Func_A4CC5784DA33517F() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
std::scoped_lock lk{g_library_mutex};
g_library_init = false;
return ORBIS_OK;
}
/**
* Aborts requests started by Func_F8E9DB52CD425743
*/
s32 PS4_SYSV_ABI Func_A507D84D91F39CC7() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
// Request logic is unimplemented, so this does nothing.
return ORBIS_OK;
}
/**
* Initializes the library
*/
s32 PS4_SYSV_ABI Func_EC2C48E74FF19429() {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
g_library_init = true;
// Also retrieves and sends compiled SDK version to server.
return ORBIS_OK;
}
/**
* Creates an NP request to determine if the user has a subscription to EA's services.
*/
s32 PS4_SYSV_ABI Func_F8E9DB52CD425743(UserService::OrbisUserServiceUserId user_id, bool* result) {
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
if (!g_library_init) {
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
}
if (result == nullptr) {
return ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT;
}
std::scoped_lock lk{g_library_mutex};
// In the real library, this creates and sends a request that checks for EA subscription,
// then waits for the request to return a response, and returns that response.
// NP signed out likely returns an error, but I haven't figured out the error code yet.
// For now, stub having no subscription.
*result = false;
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("pMxXhNozUX8", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_A4CC5784DA33517F);
LIB_FUNCTION("pQfYTZHznMc", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_A507D84D91F39CC7);
LIB_FUNCTION("7CxI50-xlCk", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_EC2C48E74FF19429);
LIB_FUNCTION("+OnbUs1CV0M", "libSceNpPartner001", 1, "libSceNpPartner001",
Func_F8E9DB52CD425743);
};
} // namespace Libraries::Np::NpPartner

View File

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2025 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::NpPartner {
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpPartner

View File

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
constexpr int ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED = 0x819d0001;
constexpr int ORBIS_NP_PARTNER_ERROR_INVALID_ARGUMENT = 0x819d0002;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,158 @@
// SPDX-FileCopyrightText: Copyright 2024-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::NpTus {
s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtx();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariable();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUser();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtx();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotData();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariable();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetData();
s32 PS4_SYSV_ABI sceNpTusGetDataAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataVUser();
s32 PS4_SYSV_ABI sceNpTusGetDataVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatus();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariable();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatus();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariable();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatus();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariable();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetData();
s32 PS4_SYSV_ABI sceNpTusSetDataAsync();
s32 PS4_SYSV_ABI sceNpTusSetDataVUser();
s32 PS4_SYSV_ABI sceNpTusSetDataVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariable();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariable();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUser();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTssCreateNpTitleCtxA();
s32 PS4_SYSV_ABI sceNpTssGetData();
s32 PS4_SYSV_ABI sceNpTssGetDataAsync();
s32 PS4_SYSV_ABI sceNpTssGetSmallStorage();
s32 PS4_SYSV_ABI sceNpTssGetSmallStorageAsync();
s32 PS4_SYSV_ABI sceNpTssGetStorage();
s32 PS4_SYSV_ABI sceNpTssGetStorageAsync();
s32 PS4_SYSV_ABI sceNpTusAbortRequest();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableA();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusAddAndGetVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusChangeModeForOtherSaveDataOwners();
s32 PS4_SYSV_ABI sceNpTusCreateNpTitleCtxA();
s32 PS4_SYSV_ABI sceNpTusCreateRequest();
s32 PS4_SYSV_ABI sceNpTusCreateTitleCtx();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataA();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataAAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUser();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotDataVUserAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableA();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUser();
s32 PS4_SYSV_ABI sceNpTusDeleteMultiSlotVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusDeleteNpTitleCtx();
s32 PS4_SYSV_ABI sceNpTusDeleteRequest();
s32 PS4_SYSV_ABI sceNpTusGetDataA();
s32 PS4_SYSV_ABI sceNpTusGetDataAAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataAVUser();
s32 PS4_SYSV_ABI sceNpTusGetDataAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetDataForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusA();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusAAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetFriendsDataStatusForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableA();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetFriendsVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusA();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotDataStatusForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableA();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiSlotVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusA();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserDataStatusForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableA();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusGetMultiUserVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusPollAsync();
s32 PS4_SYSV_ABI sceNpTusSetDataA();
s32 PS4_SYSV_ABI sceNpTusSetDataAAsync();
s32 PS4_SYSV_ABI sceNpTusSetDataAVUser();
s32 PS4_SYSV_ABI sceNpTusSetDataAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableA();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUser();
s32 PS4_SYSV_ABI sceNpTusSetMultiSlotVariableVUserAsync();
s32 PS4_SYSV_ABI sceNpTusSetThreadParam();
s32 PS4_SYSV_ABI sceNpTusSetTimeout();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableA();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUser();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableAVUserAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSave();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveAsync();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUser();
s32 PS4_SYSV_ABI sceNpTusTryAndSetVariableForCrossSaveVUserAsync();
s32 PS4_SYSV_ABI sceNpTusWaitAsync();
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Np::NpTus

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 "common/alignment.h"
@ -105,7 +105,7 @@ void Linker::Execute(const std::vector<std::string>& args) {
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
main_thread.Run([this, module, &args](std::stop_token) {
Common::SetCurrentThreadName("GAME_MainThread");
Common::SetCurrentThreadName("Game:Main");
if (auto& ipc = IPC::Instance()) {
ipc.WaitForStart();
}

View File

@ -79,6 +79,7 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
return size;
}
std::shared_lock lk{mutex};
ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -117,6 +118,7 @@ void MemoryManager::SetPrtArea(u32 id, VAddr address, u64 size) {
}
void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
std::shared_lock lk{mutex};
ASSERT_MSG(IsValidMapping(virtual_addr), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -137,6 +139,7 @@ void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
bool MemoryManager::TryWriteBacking(void* address, const void* data, u64 size) {
const VAddr virtual_addr = std::bit_cast<VAddr>(address);
std::shared_lock lk{mutex};
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -263,9 +266,7 @@ s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) {
return ORBIS_OK;
}
// Lock mutex
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
// If this is a checked free, then all direct memory in range must be allocated.
std::vector<std::pair<PAddr, u64>> free_list;
u64 remaining_size = size;
@ -316,6 +317,17 @@ s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) {
}
}
}
// Early unmap from GPU to avoid deadlocking.
for (auto& [addr, unmap_size] : remove_list) {
if (IsValidGpuMapping(addr, unmap_size)) {
rasterizer->UnmapMemory(addr, unmap_size);
}
}
// Acquire writer lock
std::scoped_lock lk2{mutex};
for (const auto& [addr, size] : remove_list) {
LOG_INFO(Kernel_Vmm, "Unmapping direct mapping {:#x} with size {:#x}", addr, size);
UnmapMemoryImpl(addr, size);
@ -337,7 +349,7 @@ s32 MemoryManager::Free(PAddr phys_addr, u64 size, bool is_checked) {
}
s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -429,54 +441,31 @@ s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32
return ORBIS_OK;
}
std::pair<s32, MemoryManager::VMAHandle> MemoryManager::CreateArea(
VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, VMAType type,
std::string_view name, u64 alignment) {
// Limit the minimum address to SystemManagedVirtualBase to prevent hardware-specific issues.
VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr;
// Fixed mapping means the virtual address must exactly match the provided one.
// On a PS4, the Fixed flag is ignored if address 0 is provided.
if (True(flags & MemoryMapFlags::Fixed) && virtual_addr != 0) {
ASSERT_MSG(IsValidMapping(mapped_addr, size), "Attempted to access invalid address {:#x}",
mapped_addr);
auto vma = FindVMA(mapped_addr)->second;
// There's a possible edge case where we're mapping to a partially reserved range.
// To account for this, unmap any reserved areas within this mapping range first.
auto unmap_addr = mapped_addr;
MemoryManager::VMAHandle MemoryManager::CreateArea(VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, VMAType type,
std::string_view name, u64 alignment) {
// Locate the VMA representing the requested region
auto vma = FindVMA(virtual_addr)->second;
if (True(flags & MemoryMapFlags::Fixed)) {
// If fixed is specified, map directly to the region of virtual_addr + size.
// Callers should check to ensure the NoOverwrite flag is handled appropriately beforehand.
auto unmap_addr = virtual_addr;
auto unmap_size = size;
// If flag NoOverwrite is provided, don't overwrite mapped VMAs.
// When it isn't provided, VMAs can be overwritten regardless of if they're mapped.
while ((False(flags & MemoryMapFlags::NoOverwrite) || vma.IsFree()) &&
unmap_addr < mapped_addr + size) {
while (unmap_size > 0) {
auto unmapped = UnmapBytesFromEntry(unmap_addr, vma, unmap_size);
unmap_addr += unmapped;
unmap_size -= unmapped;
vma = FindVMA(unmap_addr)->second;
}
vma = FindVMA(mapped_addr)->second;
auto remaining_size = vma.base + vma.size - mapped_addr;
if (!vma.IsFree() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, mapped_addr);
return {ORBIS_KERNEL_ERROR_ENOMEM, vma_map.end()};
}
} else {
// When MemoryMapFlags::Fixed is not specified, and mapped_addr is 0,
// search from address 0x200000000 instead.
alignment = alignment > 0 ? alignment : 16_KB;
mapped_addr = virtual_addr == 0 ? 0x200000000 : mapped_addr;
mapped_addr = SearchFree(mapped_addr, size, alignment);
if (mapped_addr == -1) {
// No suitable memory areas to map to
return {ORBIS_KERNEL_ERROR_ENOMEM, vma_map.end()};
}
}
vma = FindVMA(virtual_addr)->second;
// By this point, vma should be free and ready to map.
// Caller performs address searches for non-fixed mappings before this.
ASSERT_MSG(vma.IsFree(), "VMA to map is not free");
// Create a memory area representing this mapping.
const auto new_vma_handle = CarveVMA(mapped_addr, size);
const auto new_vma_handle = CarveVMA(virtual_addr, size);
auto& new_vma = new_vma_handle->second;
const bool is_exec = True(prot & MemoryProt::CpuExec);
if (True(prot & MemoryProt::CpuWrite)) {
@ -484,12 +473,13 @@ std::pair<s32, MemoryManager::VMAHandle> MemoryManager::CreateArea(
prot |= MemoryProt::CpuRead;
}
// Update VMA appropriately.
new_vma.disallow_merge = True(flags & MemoryMapFlags::NoCoalesce);
new_vma.prot = prot;
new_vma.name = name;
new_vma.type = type;
new_vma.phys_areas.clear();
return {ORBIS_OK, new_vma_handle};
return new_vma_handle;
}
s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
@ -504,8 +494,7 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
total_flexible_size - flexible_usage, size);
return ORBIS_KERNEL_ERROR_EINVAL;
}
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
PhysHandle dmem_area;
// Validate the requested physical address range
@ -538,12 +527,37 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
}
}
auto [result, new_vma_handle] =
CreateArea(virtual_addr, size, prot, flags, type, name, alignment);
if (result != ORBIS_OK) {
return result;
if (True(flags & MemoryMapFlags::Fixed) && True(flags & MemoryMapFlags::NoOverwrite)) {
// Perform necessary error checking for Fixed & NoOverwrite case
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
auto vma = FindVMA(virtual_addr)->second;
auto remaining_size = vma.base + vma.size - virtual_addr;
if (!vma.IsFree() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, virtual_addr);
return ORBIS_KERNEL_ERROR_ENOMEM;
}
} else if (False(flags & MemoryMapFlags::Fixed)) {
// Find a free virtual addr to map
alignment = alignment > 0 ? alignment : 16_KB;
virtual_addr = virtual_addr == 0 ? DEFAULT_MAPPING_BASE : virtual_addr;
virtual_addr = SearchFree(virtual_addr, size, alignment);
if (virtual_addr == -1) {
// No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
// Perform early GPU unmap to avoid potential deadlocks
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Acquire writer lock.
std::scoped_lock lk2{mutex};
// Create VMA representing this mapping.
auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, type, name, alignment);
auto& new_vma = new_vma_handle->second;
auto mapped_addr = new_vma.base;
bool is_exec = True(prot & MemoryProt::CpuExec);
@ -590,7 +604,7 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
// Map the physical memory for this direct memory mapping.
auto phys_addr_to_search = phys_addr;
u64 remaining_size = size;
dmem_area = FindDmemArea(phys_addr);
auto dmem_area = FindDmemArea(phys_addr);
while (dmem_area != dmem_map.end() && remaining_size > 0) {
// Carve a new dmem area in place of this one with the appropriate type.
// Ensure the carved area only covers the current dmem area.
@ -638,14 +652,15 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo
rasterizer->MapMemory(mapped_addr, size);
}
}
return ORBIS_OK;
}
s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot,
MemoryMapFlags flags, s32 fd, s64 phys_addr) {
std::scoped_lock lk{mutex};
uintptr_t handle = 0;
std::scoped_lock lk{unmap_mutex};
// Get the file to map
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(fd);
if (file == nullptr) {
@ -663,12 +678,13 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
prot |= MemoryProt::CpuRead;
}
const auto handle = file->f.GetFileMapping();
handle = file->f.GetFileMapping();
if (False(file->f.GetAccessMode() & Common::FS::FileAccessMode::Write) ||
False(file->f.GetAccessMode() & Common::FS::FileAccessMode::Append)) {
// If the file does not have write access, ensure prot does not contain write permissions.
// On real hardware, these mappings succeed, but the memory cannot be written to.
// If the file does not have write access, ensure prot does not contain write
// permissions. On real hardware, these mappings succeed, but the memory cannot be
// written to.
prot &= ~MemoryProt::CpuWrite;
}
@ -682,13 +698,38 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
prot &= ~MemoryProt::CpuExec;
}
auto [result, new_vma_handle] =
CreateArea(virtual_addr, size, prot, flags, VMAType::File, "anon", 0);
if (result != ORBIS_OK) {
return result;
if (True(flags & MemoryMapFlags::Fixed) && False(flags & MemoryMapFlags::NoOverwrite)) {
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
auto vma = FindVMA(virtual_addr)->second;
auto remaining_size = vma.base + vma.size - virtual_addr;
if (!vma.IsFree() || remaining_size < size) {
LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, virtual_addr);
return ORBIS_KERNEL_ERROR_ENOMEM;
}
} else if (False(flags & MemoryMapFlags::Fixed)) {
virtual_addr = virtual_addr == 0 ? DEFAULT_MAPPING_BASE : virtual_addr;
virtual_addr = SearchFree(virtual_addr, size, 16_KB);
if (virtual_addr == -1) {
// No suitable memory areas to map to
return ORBIS_KERNEL_ERROR_ENOMEM;
}
}
// Perform early GPU unmap to avoid potential deadlocks
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Aquire writer lock
std::scoped_lock lk2{mutex};
// Update VMA map and map to address space.
auto new_vma_handle = CreateArea(virtual_addr, size, prot, flags, VMAType::File, "anon", 0);
auto& new_vma = new_vma_handle->second;
new_vma.fd = fd;
auto mapped_addr = new_vma.base;
bool is_exec = True(prot & MemoryProt::CpuExec);
@ -699,7 +740,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory
}
s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
@ -713,6 +754,14 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
it++;
}
// Perform early GPU unmap to avoid potential deadlocks
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Aquire writer mutex
std::scoped_lock lk2{mutex};
// Loop through all vmas in the area, unmap them.
u64 remaining_size = size;
VAddr current_addr = virtual_addr;
@ -721,13 +770,7 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
const auto& vma_base = handle->second;
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;
@ -772,7 +815,7 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) {
}
// Unmap from address space
impl.Unmap(virtual_addr, size, true);
impl.Unmap(virtual_addr, size);
// Tracy memory tracking breaks from merging memory areas. Disabled for now.
// TRACK_FREE(virtual_addr, "VMEM");
@ -783,29 +826,32 @@ s32 MemoryManager::UnmapMemory(VAddr virtual_addr, u64 size) {
if (size == 0) {
return ORBIS_OK;
}
std::scoped_lock lk{mutex};
std::scoped_lock lk{unmap_mutex};
// Align address and size appropriately
virtual_addr = Common::AlignDown(virtual_addr, 16_KB);
size = Common::AlignUp(size, 16_KB);
ASSERT_MSG(IsValidMapping(virtual_addr, size), "Attempted to access invalid address {:#x}",
virtual_addr);
u64 bytes_unmapped = UnmapMemoryImpl(virtual_addr, size);
return bytes_unmapped;
// If the requested range has GPU access, unmap from GPU.
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
// Acquire writer lock.
std::scoped_lock lk2{mutex};
return UnmapMemoryImpl(virtual_addr, size);
}
u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size) {
const auto start_in_vma = virtual_addr - vma_base.base;
const auto size_in_vma = std::min<u64>(vma_base.size - start_in_vma, size);
const auto vma_type = vma_base.type;
const bool has_backing = HasPhysicalBacking(vma_base) || vma_base.type == VMAType::File;
const bool readonly_file =
vma_base.prot == MemoryProt::CpuRead && vma_base.type == VMAType::File;
const bool is_exec = True(vma_base.prot & MemoryProt::CpuExec);
if (vma_base.type == VMAType::Free || vma_base.type == VMAType::Pooled) {
return size_in_vma;
}
PAddr phys_base = 0;
VAddr current_addr = virtual_addr;
if (vma_base.phys_areas.size() > 0) {
u64 size_to_free = size_in_vma;
@ -860,14 +906,9 @@ u64 MemoryManager::UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma
if (vma_type != VMAType::Reserved && vma_type != VMAType::PoolReserved) {
// Unmap the memory region.
impl.Unmap(virtual_addr, size_in_vma, has_backing);
impl.Unmap(virtual_addr, size_in_vma);
// Tracy memory tracking breaks from merging memory areas. Disabled for now.
// TRACK_FREE(virtual_addr, "VMEM");
// If this mapping has GPU access, unmap from GPU.
if (IsValidGpuMapping(virtual_addr, size)) {
rasterizer->UnmapMemory(virtual_addr, size);
}
}
return size_in_vma;
}
@ -983,7 +1024,7 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) {
}
// Ensure the range to modify is valid
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr);
// Appropriately restrict flags.
@ -1141,7 +1182,7 @@ s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u6
}
s32 MemoryManager::SetDirectMemoryType(VAddr addr, u64 size, s32 memory_type) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
ASSERT_MSG(IsValidMapping(addr, size), "Attempted to access invalid address {:#x}", addr);
@ -1188,7 +1229,7 @@ s32 MemoryManager::SetDirectMemoryType(VAddr addr, u64 size, s32 memory_type) {
}
void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name) {
std::scoped_lock lk{mutex};
std::scoped_lock lk{mutex, unmap_mutex};
// Sizes are aligned up to the nearest 16_KB
u64 aligned_size = Common::AlignUp(size, 16_KB);
@ -1246,7 +1287,6 @@ s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) {
ASSERT_MSG(IsValidMapping(addr), "Attempted to access invalid address {:#x}", addr);
const auto& vma = FindVMA(addr)->second;
if (vma.IsFree()) {
mutex.unlock_shared();
return ORBIS_KERNEL_ERROR_EACCES;
}

View File

@ -28,6 +28,8 @@ class MemoryMapViewer;
namespace Core {
constexpr u64 DEFAULT_MAPPING_BASE = 0x200000000;
enum class MemoryProt : u32 {
NoAccess = 0,
CpuRead = 1,
@ -304,10 +306,8 @@ private:
vma.type == VMAType::Pooled;
}
std::pair<s32, MemoryManager::VMAHandle> CreateArea(VAddr virtual_addr, u64 size,
MemoryProt prot, MemoryMapFlags flags,
VMAType type, std::string_view name,
u64 alignment);
VMAHandle CreateArea(VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags,
VMAType type, std::string_view name, u64 alignment);
VAddr SearchFree(VAddr virtual_addr, u64 size, u32 alignment);
@ -333,6 +333,7 @@ private:
PhysMap fmem_map;
VMAMap vma_map;
Common::SharedFirstMutex mutex{};
std::mutex unmap_mutex{};
u64 total_direct_size{};
u64 total_flexible_size{};
u64 flexible_usage{};

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 <filesystem>
@ -96,7 +96,7 @@ s32 ReadCompiledSdkVersion(const std::filesystem::path& file) {
void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
std::optional<std::filesystem::path> p_game_folder) {
Common::SetCurrentThreadName("Main Thread");
Common::SetCurrentThreadName("shadPS4:Main");
if (waitForDebuggerBeforeRun) {
Debugger::WaitForDebuggerAttach();
}

View File

@ -219,6 +219,7 @@ public:
void V_NOT_B32(const GcnInst& inst);
void V_BFREV_B32(const GcnInst& inst);
void V_FFBH_U32(const GcnInst& inst);
void V_FFBH_I32(const GcnInst& inst);
void V_FFBL_B32(const GcnInst& inst);
void V_FREXP_EXP_I32_F64(const GcnInst& inst);
void V_FREXP_MANT_F64(const GcnInst& inst);
@ -231,6 +232,7 @@ public:
// VOPC
void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst);
void V_CMP_F64(ConditionOp op, bool set_exec, const GcnInst& inst);
void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst);
void V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst);
void V_CMP_CLASS_F32(const GcnInst& inst);
@ -259,6 +261,7 @@ public:
void V_CVT_PK_I16_I32(const GcnInst& inst);
void V_CVT_PK_U8_F32(const GcnInst& inst);
void V_LSHL_B64(const GcnInst& inst);
void V_LSHR_B64(const GcnInst& inst);
void V_ALIGNBIT_B32(const GcnInst& inst);
void V_ALIGNBYTE_B32(const GcnInst& inst);
void V_MUL_F64(const GcnInst& inst);

View File

@ -188,6 +188,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_FFBH_U32(inst);
case Opcode::V_FFBL_B32:
return V_FFBL_B32(inst);
case Opcode::V_FFBH_I32:
return V_FFBH_I32(inst);
case Opcode::V_FREXP_EXP_I32_F64:
return V_FREXP_EXP_I32_F64(inst);
case Opcode::V_FREXP_MANT_F64:
@ -264,6 +266,34 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
case Opcode::V_CMPX_TRU_F32:
return V_CMP_F32(ConditionOp::TRU, true, inst);
// V_CMP_{OP16}_F64
case Opcode::V_CMP_F_F64:
return V_CMP_F64(ConditionOp::F, false, inst);
case Opcode::V_CMP_LT_F64:
return V_CMP_F64(ConditionOp::LT, false, inst);
case Opcode::V_CMP_EQ_F64:
return V_CMP_F64(ConditionOp::EQ, false, inst);
case Opcode::V_CMP_LE_F64:
return V_CMP_F64(ConditionOp::LE, false, inst);
case Opcode::V_CMP_GT_F64:
return V_CMP_F64(ConditionOp::GT, false, inst);
case Opcode::V_CMP_LG_F64:
return V_CMP_F64(ConditionOp::LG, false, inst);
case Opcode::V_CMP_GE_F64:
return V_CMP_F64(ConditionOp::GE, false, inst);
case Opcode::V_CMP_U_F64:
return V_CMP_F64(ConditionOp::U, false, inst);
case Opcode::V_CMP_NGE_F64:
return V_CMP_F64(ConditionOp::LT, false, inst);
case Opcode::V_CMP_NGT_F64:
return V_CMP_F64(ConditionOp::LE, false, inst);
case Opcode::V_CMP_NLE_F64:
return V_CMP_F64(ConditionOp::GT, false, inst);
case Opcode::V_CMP_NEQ_F64:
return V_CMP_F64(ConditionOp::LG, false, inst);
case Opcode::V_CMP_NLT_F64:
return V_CMP_F64(ConditionOp::GE, false, inst);
// V_CMP_{OP8}_I32
case Opcode::V_CMP_LT_I32:
return V_CMP_U32(ConditionOp::LT, true, false, inst);
@ -394,6 +424,8 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_CVT_PK_U8_F32(inst);
case Opcode::V_LSHL_B64:
return V_LSHL_B64(inst);
case Opcode::V_LSHR_B64:
return V_LSHR_B64(inst);
case Opcode::V_ADD_F64:
return V_ADD_F64(inst);
case Opcode::V_ALIGNBIT_B32:
@ -918,6 +950,19 @@ void Translator::V_FFBL_B32(const GcnInst& inst) {
SetDst(inst.dst[0], ir.FindILsb(src0));
}
void Translator::V_FFBH_I32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
// Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB)
// position
const IR::U32 msb_pos = ir.FindSMsb(src0);
const IR::U32 pos_from_left = ir.ISub(ir.Imm32(31), msb_pos);
// Select 0xFFFFFFFF if src0 was 0 or -1
const IR::U32 minusOne = ir.Imm32(~0U);
const IR::U1 cond =
ir.LogicalAnd(ir.INotEqual(src0, ir.Imm32(0)), ir.INotEqual(src0, minusOne));
SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, minusOne)});
}
void Translator::V_FREXP_EXP_I32_F64(const GcnInst& inst) {
const IR::F64 src0{GetSrc64<IR::F64>(inst.src[0])};
SetDst(inst.dst[0], ir.FPFrexpExp(src0));
@ -1011,6 +1056,47 @@ void Translator::V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst) {
}
}
void Translator::V_CMP_F64(ConditionOp op, bool set_exec, const GcnInst& inst) {
const IR::F64 src0{GetSrc64<IR::F64>(inst.src[0])};
const IR::F64 src1{GetSrc64<IR::F64>(inst.src[1])};
const IR::U1 result = [&] {
switch (op) {
case ConditionOp::F:
return ir.Imm1(false);
case ConditionOp::EQ:
return ir.FPEqual(src0, src1);
case ConditionOp::LG:
return ir.FPNotEqual(src0, src1);
case ConditionOp::GT:
return ir.FPGreaterThan(src0, src1);
case ConditionOp::LT:
return ir.FPLessThan(src0, src1);
case ConditionOp::LE:
return ir.FPLessThanEqual(src0, src1);
case ConditionOp::GE:
return ir.FPGreaterThanEqual(src0, src1);
case ConditionOp::U:
return ir.LogicalOr(ir.FPIsNan(src0), ir.FPIsNan(src1));
default:
UNREACHABLE();
}
}();
if (set_exec) {
ir.SetExec(result);
}
switch (inst.dst[1].field) {
case OperandField::VccLo:
ir.SetVcc(result);
break;
case OperandField::ScalarGPR:
ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result);
break;
default:
UNREACHABLE();
}
}
void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
@ -1357,6 +1443,12 @@ void Translator::V_LSHL_B64(const GcnInst& inst) {
SetDst64(inst.dst[0], ir.ShiftLeftLogical(src0, ir.BitwiseAnd(src1, ir.Imm64(u64(0x3F)))));
}
void Translator::V_LSHR_B64(const GcnInst& inst) {
const IR::U64 src0{GetSrc64(inst.src[0])};
const IR::U64 src1{GetSrc64(inst.src[1])};
SetDst64(inst.dst[0], ir.ShiftRightLogical(src0, ir.BitwiseAnd(src1, ir.Imm64(u64(0x3F)))));
}
void Translator::V_ALIGNBIT_B32(const GcnInst& inst) {
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};