mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-09 02:51:35 -06:00
Merge branch 'main' into user_and_settings
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
Some checks failed
Build and Release / reuse (push) Has been cancelled
Build and Release / clang-format (push) Has been cancelled
Build and Release / get-info (push) Has been cancelled
Build and Release / windows-sdl (push) Has been cancelled
Build and Release / macos-sdl (push) Has been cancelled
Build and Release / linux-sdl (push) Has been cancelled
Build and Release / linux-sdl-gcc (push) Has been cancelled
Build and Release / pre-release (push) Has been cancelled
This commit is contained in:
commit
8c17de032d
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -123,3 +123,7 @@
|
||||
[submodule "externals/aacdec/fdk-aac"]
|
||||
path = externals/aacdec/fdk-aac
|
||||
url = https://android.googlesource.com/platform/external/aac
|
||||
[submodule "externals/ext-CLI11"]
|
||||
path = externals/ext-CLI11
|
||||
url = https://github.com/shadexternals/ext-CLI11.git
|
||||
branch = main
|
||||
|
||||
@ -920,6 +920,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/profile.h
|
||||
src/shader_recompiler/ir/passes/flatten_extended_userdata_pass.cpp
|
||||
src/shader_recompiler/ir/passes/hull_shader_transform.cpp
|
||||
src/shader_recompiler/ir/passes/identity_removal_pass.cpp
|
||||
src/shader_recompiler/ir/passes/inject_clip_distance_attributes.cpp
|
||||
src/shader_recompiler/ir/passes/ir_passes.h
|
||||
src/shader_recompiler/ir/passes/lower_buffer_format_to_raw.cpp
|
||||
src/shader_recompiler/ir/passes/lower_fp64_to_fp32.cpp
|
||||
@ -1101,7 +1102,7 @@ create_target_directory_groups(shadps4)
|
||||
|
||||
target_link_libraries(shadps4 PRIVATE magic_enum::magic_enum fmt::fmt toml11::toml11 tsl::robin_map xbyak::xbyak Tracy::TracyClient RenderDoc::API FFmpeg::ffmpeg Dear_ImGui gcn half::half ZLIB::ZLIB PNG::PNG)
|
||||
target_link_libraries(shadps4 PRIVATE Boost::headers GPUOpen::VulkanMemoryAllocator LibAtrac9 sirit Vulkan::Headers xxHash::xxhash Zydis::Zydis glslang::glslang SDL3::SDL3 SDL3_mixer::SDL3_mixer pugixml::pugixml)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac)
|
||||
target_link_libraries(shadps4 PRIVATE stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz fdk-aac CLI11::CLI11)
|
||||
|
||||
target_compile_definitions(shadps4 PRIVATE IMGUI_USER_CONFIG="imgui/imgui_config.h")
|
||||
target_compile_definitions(Dear_ImGui PRIVATE IMGUI_USER_CONFIG="${PROJECT_SOURCE_DIR}/src/imgui/imgui_config.h")
|
||||
|
||||
@ -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).
|
||||
|
||||
45
documents/Docker Builder/.devcontainer/devcontainer.json
Normal file
45
documents/Docker Builder/.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,45 @@
|
||||
// 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"
|
||||
],
|
||||
"settings": {
|
||||
"C_Cpp.intelliSenseEngine": "disabled",
|
||||
"clangd.arguments": [
|
||||
"--background-index",
|
||||
"--clang-tidy",
|
||||
"--completion-style=detailed",
|
||||
"--header-insertion=never"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"cmake.configureOnOpen": false,
|
||||
"cmake.generator": "Unix Makefiles",
|
||||
"cmake.environment": {
|
||||
"CC": "clang",
|
||||
"CXX": "clang++"
|
||||
},
|
||||
"cmake.configureSettings": {
|
||||
"CMAKE_CXX_STANDARD": "23",
|
||||
"CMAKE_CXX_STANDARD_REQUIRED": "ON"
|
||||
}
|
||||
}
|
||||
}
|
||||
38
documents/Docker Builder/.docker/Dockerfile
Normal file
38
documents/Docker Builder/.docker/Dockerfile
Normal file
@ -0,0 +1,38 @@
|
||||
# SPDX-FileCopyrightText: 2026 shadPS4 Emulator Project
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
clang \
|
||||
git \
|
||||
ca-certificates \
|
||||
wget \
|
||||
libasound2-dev \
|
||||
libpulse-dev \
|
||||
libopenal-dev \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libedit-dev \
|
||||
libudev-dev \
|
||||
libevdev-dev \
|
||||
libsdl2-dev \
|
||||
libjack-dev \
|
||||
libsndio-dev \
|
||||
libxtst-dev \
|
||||
libvulkan-dev \
|
||||
vulkan-validationlayers \
|
||||
libpng-dev \
|
||||
clang-tidy \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN wget -qO - https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor -o /usr/share/keyrings/kitware-archive-keyring.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ noble main" > /etc/apt/sources.list.d/kitware.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y cmake \
|
||||
&& rm -rf /var/lib/apt/lists/*/*
|
||||
|
||||
WORKDIR /workspaces/shadPS4
|
||||
10
documents/Docker Builder/docker-compose.yml
Normal file
10
documents/Docker Builder/docker-compose.yml
Normal 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
|
||||
91
documents/building-docker.md
Normal file
91
documents/building-docker.md
Normal file
@ -0,0 +1,91 @@
|
||||
<!--
|
||||
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
|
||||
|
||||
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 don’t 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.
|
||||
9
externals/CMakeLists.txt
vendored
9
externals/CMakeLists.txt
vendored
@ -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
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
@ -204,6 +204,7 @@ add_subdirectory(tracy)
|
||||
|
||||
# pugixml
|
||||
if (NOT TARGET pugixml::pugixml)
|
||||
option(PUGIXML_NO_EXCEPTIONS "" ON)
|
||||
add_subdirectory(pugixml)
|
||||
endif()
|
||||
|
||||
@ -268,3 +269,9 @@ add_subdirectory(json)
|
||||
|
||||
# miniz
|
||||
add_subdirectory(miniz)
|
||||
|
||||
# cli11
|
||||
set(CLI11_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(CLI11_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory(ext-CLI11)
|
||||
1
externals/ext-CLI11
vendored
Submodule
1
externals/ext-CLI11
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 1cce1483345e60997b87720948c37d6a34db2658
|
||||
@ -94,7 +94,10 @@ static u64 BackingSize = ORBIS_KERNEL_TOTAL_MEM_DEV_PRO;
|
||||
|
||||
struct MemoryRegion {
|
||||
VAddr base;
|
||||
size_t size;
|
||||
PAddr phys_base;
|
||||
u64 size;
|
||||
u32 prot;
|
||||
s32 fd;
|
||||
bool is_mapped;
|
||||
};
|
||||
|
||||
@ -160,7 +163,8 @@ struct AddressSpace::Impl {
|
||||
// Restrict region size to avoid overly fragmenting the virtual memory space.
|
||||
if (info.State == MEM_FREE && info.RegionSize > 0x1000000) {
|
||||
VAddr addr = Common::AlignUp(reinterpret_cast<VAddr>(info.BaseAddress), alignment);
|
||||
regions.emplace(addr, MemoryRegion{addr, size, false});
|
||||
regions.emplace(addr,
|
||||
MemoryRegion{addr, PAddr(-1), size, PAGE_NOACCESS, -1, false});
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,46 +212,52 @@ struct AddressSpace::Impl {
|
||||
~Impl() {
|
||||
if (virtual_base) {
|
||||
if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) {
|
||||
LOG_CRITICAL(Render, "Failed to free virtual memory");
|
||||
LOG_CRITICAL(Core, "Failed to free virtual memory");
|
||||
}
|
||||
}
|
||||
if (backing_base) {
|
||||
if (!UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) {
|
||||
LOG_CRITICAL(Render, "Failed to unmap backing memory placeholder");
|
||||
LOG_CRITICAL(Core, "Failed to unmap backing memory placeholder");
|
||||
}
|
||||
if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) {
|
||||
LOG_CRITICAL(Render, "Failed to free backing memory");
|
||||
LOG_CRITICAL(Core, "Failed to free backing memory");
|
||||
}
|
||||
}
|
||||
if (!CloseHandle(backing_handle)) {
|
||||
LOG_CRITICAL(Render, "Failed to free backing memory file handle");
|
||||
LOG_CRITICAL(Core, "Failed to free backing memory file handle");
|
||||
}
|
||||
}
|
||||
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, ULONG prot, uintptr_t fd = 0) {
|
||||
// Before mapping we must carve a placeholder with the exact properties of our mapping.
|
||||
auto* region = EnsureSplitRegionForMapping(virtual_addr, size);
|
||||
region->is_mapped = true;
|
||||
void* MapRegion(MemoryRegion* region) {
|
||||
VAddr virtual_addr = region->base;
|
||||
PAddr phys_addr = region->phys_base;
|
||||
u64 size = region->size;
|
||||
ULONG prot = region->prot;
|
||||
s32 fd = region->fd;
|
||||
|
||||
void* ptr = nullptr;
|
||||
if (phys_addr != -1) {
|
||||
HANDLE backing = fd ? reinterpret_cast<HANDLE>(fd) : backing_handle;
|
||||
if (fd && prot == PAGE_READONLY) {
|
||||
HANDLE backing = fd != -1 ? reinterpret_cast<HANDLE>(fd) : backing_handle;
|
||||
if (fd != -1 && prot == PAGE_READONLY) {
|
||||
DWORD resultvar;
|
||||
ptr = VirtualAlloc2(process, reinterpret_cast<PVOID>(virtual_addr), size,
|
||||
MEM_RESERVE | MEM_COMMIT | MEM_REPLACE_PLACEHOLDER,
|
||||
PAGE_READWRITE, nullptr, 0);
|
||||
bool ret = ReadFile(backing, ptr, size, &resultvar, NULL);
|
||||
|
||||
// phys_addr serves as an offset for file mmaps.
|
||||
// Create an OVERLAPPED with the offset, then supply that to ReadFile
|
||||
OVERLAPPED param{};
|
||||
// Offset is the least-significant 32 bits, OffsetHigh is the most-significant.
|
||||
param.Offset = phys_addr & 0xffffffffull;
|
||||
param.OffsetHigh = (phys_addr & 0xffffffff00000000ull) >> 32;
|
||||
bool ret = ReadFile(backing, ptr, size, &resultvar, ¶m);
|
||||
ASSERT_MSG(ret, "ReadFile failed. {}", Common::GetLastErrorMsg());
|
||||
ret = VirtualProtect(ptr, size, prot, &resultvar);
|
||||
ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg());
|
||||
} else {
|
||||
ptr = MapViewOfFile3(backing, process, reinterpret_cast<PVOID>(virtual_addr),
|
||||
phys_addr, size, MEM_REPLACE_PLACEHOLDER,
|
||||
PAGE_EXECUTE_READWRITE, nullptr, 0);
|
||||
phys_addr, size, MEM_REPLACE_PLACEHOLDER, prot, nullptr, 0);
|
||||
ASSERT_MSG(ptr, "MapViewOfFile3 failed. {}", Common::GetLastErrorMsg());
|
||||
DWORD resultvar;
|
||||
bool ret = VirtualProtect(ptr, size, prot, &resultvar);
|
||||
ASSERT_MSG(ret, "VirtualProtect failed. {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
} else {
|
||||
ptr =
|
||||
@ -258,135 +268,220 @@ struct AddressSpace::Impl {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void Unmap(VAddr virtual_addr, size_t size, bool has_backing) {
|
||||
bool ret;
|
||||
if (has_backing) {
|
||||
void UnmapRegion(MemoryRegion* region) {
|
||||
VAddr virtual_addr = region->base;
|
||||
PAddr phys_base = region->phys_base;
|
||||
u64 size = region->size;
|
||||
ULONG prot = region->prot;
|
||||
s32 fd = region->fd;
|
||||
|
||||
bool ret = false;
|
||||
if ((fd != -1 && prot != PAGE_READONLY) || (fd == -1 && phys_base != -1)) {
|
||||
ret = UnmapViewOfFile2(process, reinterpret_cast<PVOID>(virtual_addr),
|
||||
MEM_PRESERVE_PLACEHOLDER);
|
||||
} else {
|
||||
ret = VirtualFreeEx(process, reinterpret_cast<PVOID>(virtual_addr), size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
|
||||
}
|
||||
ASSERT_MSG(ret, "Unmap operation on virtual_addr={:#X} failed: {}", virtual_addr,
|
||||
ASSERT_MSG(ret, "Unmap on virtual_addr {:#x}, size {:#x} failed: {}", virtual_addr, size,
|
||||
Common::GetLastErrorMsg());
|
||||
|
||||
// The unmap call will create a new placeholder region. We need to see if we can coalesce it
|
||||
// with neighbors.
|
||||
JoinRegionsAfterUnmap(virtual_addr, size);
|
||||
}
|
||||
|
||||
// The following code is inspired from Dolphin's MemArena
|
||||
// https://github.com/dolphin-emu/dolphin/blob/deee3ee4/Source/Core/Common/MemArenaWin.cpp#L212
|
||||
MemoryRegion* EnsureSplitRegionForMapping(VAddr address, size_t size) {
|
||||
// Find closest region that is <= the given address by using upper bound and decrementing
|
||||
auto it = regions.upper_bound(address);
|
||||
ASSERT_MSG(it != regions.begin(), "Invalid address {:#x}", address);
|
||||
--it;
|
||||
ASSERT_MSG(!it->second.is_mapped,
|
||||
"Attempt to map {:#x} with size {:#x} which overlaps with {:#x} mapping",
|
||||
address, size, it->second.base);
|
||||
auto& [base, region] = *it;
|
||||
void SplitRegion(VAddr virtual_addr, u64 size) {
|
||||
// First, get the region this range covers
|
||||
auto it = std::prev(regions.upper_bound(virtual_addr));
|
||||
|
||||
const VAddr mapping_address = region.base;
|
||||
const size_t region_size = region.size;
|
||||
if (mapping_address == address) {
|
||||
// If this region is already split up correctly we don't have to do anything
|
||||
if (region_size == size) {
|
||||
return ®ion;
|
||||
// All unmapped areas will coalesce, so there should be a region
|
||||
// containing the full requested range. If not, then something is mapped here.
|
||||
ASSERT_MSG(it->second.base + it->second.size >= virtual_addr + size,
|
||||
"Cannot fit region into one placeholder");
|
||||
|
||||
// If the region is mapped, we need to unmap first before we can modify the placeholders.
|
||||
if (it->second.is_mapped) {
|
||||
ASSERT_MSG(it->second.phys_base != -1 || !it->second.is_mapped,
|
||||
"Cannot split unbacked mapping");
|
||||
UnmapRegion(&it->second);
|
||||
}
|
||||
|
||||
// We need to split this region to create a matching placeholder.
|
||||
if (it->second.base != virtual_addr) {
|
||||
// Requested address is not the start of the containing region,
|
||||
// create a new region to represent the memory before the requested range.
|
||||
auto& region = it->second;
|
||||
u64 base_offset = virtual_addr - region.base;
|
||||
u64 next_region_size = region.size - base_offset;
|
||||
PAddr next_region_phys_base = -1;
|
||||
if (region.is_mapped) {
|
||||
next_region_phys_base = region.phys_base + base_offset;
|
||||
}
|
||||
region.size = base_offset;
|
||||
|
||||
ASSERT_MSG(region_size >= size,
|
||||
"Region with address {:#x} and size {:#x} can't fit {:#x}", mapping_address,
|
||||
region_size, size);
|
||||
|
||||
// Split the placeholder.
|
||||
if (!VirtualFreeEx(process, LPVOID(address), size,
|
||||
// Use VirtualFreeEx to create the split.
|
||||
if (!VirtualFreeEx(process, LPVOID(region.base), region.size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
|
||||
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Update tracked mappings and return the first of the two
|
||||
// If the mapping was mapped, remap the region.
|
||||
if (region.is_mapped) {
|
||||
MapRegion(®ion);
|
||||
}
|
||||
|
||||
// Store a new region matching the removed area.
|
||||
it = regions.emplace_hint(std::next(it), virtual_addr,
|
||||
MemoryRegion(virtual_addr, next_region_phys_base,
|
||||
next_region_size, region.prot, region.fd,
|
||||
region.is_mapped));
|
||||
}
|
||||
|
||||
// At this point, the region's base will match virtual_addr.
|
||||
// Now check for a size difference.
|
||||
if (it->second.size != size) {
|
||||
// The requested size is smaller than the current region placeholder.
|
||||
// Update region to match the requested region,
|
||||
// then make a new region to represent the remaining space.
|
||||
auto& region = it->second;
|
||||
VAddr next_region_addr = region.base + size;
|
||||
u64 next_region_size = region.size - size;
|
||||
PAddr next_region_phys_base = -1;
|
||||
if (region.is_mapped) {
|
||||
next_region_phys_base = region.phys_base + size;
|
||||
}
|
||||
region.size = size;
|
||||
const VAddr new_mapping_start = address + size;
|
||||
regions.emplace_hint(std::next(it), new_mapping_start,
|
||||
MemoryRegion(new_mapping_start, region_size - size, false));
|
||||
return ®ion;
|
||||
|
||||
// Store the new region matching the remaining space
|
||||
regions.emplace_hint(std::next(it), next_region_addr,
|
||||
MemoryRegion(next_region_addr, next_region_phys_base,
|
||||
next_region_size, region.prot, region.fd,
|
||||
region.is_mapped));
|
||||
|
||||
// Use VirtualFreeEx to create the split.
|
||||
if (!VirtualFreeEx(process, LPVOID(region.base), region.size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
|
||||
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
|
||||
// If these regions were mapped, then map the unmapped area beyond the requested range.
|
||||
if (region.is_mapped) {
|
||||
MapRegion(&std::next(it)->second);
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(mapping_address < address);
|
||||
|
||||
// Is there enough space to map this?
|
||||
const size_t offset_in_region = address - mapping_address;
|
||||
const size_t minimum_size = size + offset_in_region;
|
||||
ASSERT(region_size >= minimum_size);
|
||||
|
||||
// Split the placeholder.
|
||||
if (!VirtualFreeEx(process, LPVOID(address), size,
|
||||
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
|
||||
UNREACHABLE_MSG("Region splitting failed: {}", Common::GetLastErrorMsg());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Do we now have two regions or three regions?
|
||||
if (region_size == minimum_size) {
|
||||
// Split into two; update tracked mappings and return the second one
|
||||
region.size = offset_in_region;
|
||||
it = regions.emplace_hint(std::next(it), address, MemoryRegion(address, size, false));
|
||||
return &it->second;
|
||||
} else {
|
||||
// Split into three; update tracked mappings and return the middle one
|
||||
region.size = offset_in_region;
|
||||
const VAddr middle_mapping_start = address;
|
||||
const size_t middle_mapping_size = size;
|
||||
const VAddr after_mapping_start = address + size;
|
||||
const size_t after_mapping_size = region_size - minimum_size;
|
||||
it = regions.emplace_hint(std::next(it), after_mapping_start,
|
||||
MemoryRegion(after_mapping_start, after_mapping_size, false));
|
||||
it = regions.emplace_hint(
|
||||
it, middle_mapping_start,
|
||||
MemoryRegion(middle_mapping_start, middle_mapping_size, false));
|
||||
return &it->second;
|
||||
// If the requested region was mapped, remap it.
|
||||
if (it->second.is_mapped) {
|
||||
MapRegion(&it->second);
|
||||
}
|
||||
}
|
||||
|
||||
void JoinRegionsAfterUnmap(VAddr address, size_t size) {
|
||||
// There should be a mapping that matches the request exactly, find it
|
||||
auto it = regions.find(address);
|
||||
ASSERT_MSG(it != regions.end() && it->second.size == size,
|
||||
"Invalid address/size given to unmap.");
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, ULONG prot, s32 fd = -1) {
|
||||
// Get a pointer to the region containing virtual_addr
|
||||
auto it = std::prev(regions.upper_bound(virtual_addr));
|
||||
|
||||
// If needed, split surrounding regions to create a placeholder
|
||||
if (it->first != virtual_addr || it->second.size != size) {
|
||||
SplitRegion(virtual_addr, size);
|
||||
it = std::prev(regions.upper_bound(virtual_addr));
|
||||
}
|
||||
|
||||
// Get the address and region for this range.
|
||||
auto& [base, region] = *it;
|
||||
region.is_mapped = false;
|
||||
ASSERT_MSG(!region.is_mapped, "Cannot overwrite mapped region");
|
||||
|
||||
// Check if a placeholder exists right before us.
|
||||
// Now we have a region matching the requested region, perform the actual mapping.
|
||||
region.is_mapped = true;
|
||||
region.phys_base = phys_addr;
|
||||
region.prot = prot;
|
||||
region.fd = fd;
|
||||
return MapRegion(®ion);
|
||||
}
|
||||
|
||||
void CoalesceFreeRegions(VAddr virtual_addr) {
|
||||
// First, get the region to update
|
||||
auto it = std::prev(regions.upper_bound(virtual_addr));
|
||||
ASSERT_MSG(!it->second.is_mapped, "Cannot coalesce mapped regions");
|
||||
|
||||
// Check if there are adjacent free placeholders before this area.
|
||||
bool can_coalesce = false;
|
||||
auto it_prev = it != regions.begin() ? std::prev(it) : regions.end();
|
||||
if (it_prev != regions.end() && !it_prev->second.is_mapped) {
|
||||
const size_t total_size = it_prev->second.size + size;
|
||||
if (!VirtualFreeEx(process, LPVOID(it_prev->first), total_size,
|
||||
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
|
||||
UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
|
||||
it_prev->second.size = total_size;
|
||||
while (it_prev != regions.end() && !it_prev->second.is_mapped &&
|
||||
it_prev->first + it_prev->second.size == it->first) {
|
||||
// If there is an earlier region, move our iterator to that and increase size.
|
||||
it_prev->second.size = it_prev->second.size + it->second.size;
|
||||
regions.erase(it);
|
||||
it = it_prev;
|
||||
|
||||
// Mark this region as coalesce-able.
|
||||
can_coalesce = true;
|
||||
|
||||
// Get the next previous region.
|
||||
it_prev = it != regions.begin() ? std::prev(it) : regions.end();
|
||||
}
|
||||
|
||||
// Check if a placeholder exists right after us.
|
||||
// Check if there are adjacent free placeholders after this area.
|
||||
auto it_next = std::next(it);
|
||||
if (it_next != regions.end() && !it_next->second.is_mapped) {
|
||||
const size_t total_size = it->second.size + it_next->second.size;
|
||||
if (!VirtualFreeEx(process, LPVOID(it->first), total_size,
|
||||
while (it_next != regions.end() && !it_next->second.is_mapped &&
|
||||
it->first + it->second.size == it_next->first) {
|
||||
// If there is a later region, increase our current region's size
|
||||
it->second.size = it->second.size + it_next->second.size;
|
||||
regions.erase(it_next);
|
||||
|
||||
// Mark this region as coalesce-able.
|
||||
can_coalesce = true;
|
||||
|
||||
// Get the next region
|
||||
it_next = std::next(it);
|
||||
}
|
||||
|
||||
// If there are placeholders to coalesce, then coalesce them.
|
||||
if (can_coalesce) {
|
||||
if (!VirtualFreeEx(process, LPVOID(it->first), it->second.size,
|
||||
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
|
||||
UNREACHABLE_MSG("Region coalescing failed: {}", Common::GetLastErrorMsg());
|
||||
}
|
||||
|
||||
it->second.size = total_size;
|
||||
regions.erase(it_next);
|
||||
}
|
||||
}
|
||||
|
||||
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
|
||||
void Unmap(VAddr virtual_addr, u64 size) {
|
||||
// Loop through all regions in the requested range
|
||||
u64 remaining_size = size;
|
||||
VAddr current_addr = virtual_addr;
|
||||
while (remaining_size > 0) {
|
||||
// Get a pointer to the region containing virtual_addr
|
||||
auto it = std::prev(regions.upper_bound(current_addr));
|
||||
|
||||
// If necessary, split regions to ensure a valid unmap.
|
||||
// To prevent complication, ensure size is within the bounds of the current region.
|
||||
u64 base_offset = current_addr - it->second.base;
|
||||
u64 size_to_unmap = std::min<u64>(it->second.size - base_offset, remaining_size);
|
||||
if (current_addr != it->second.base || size_to_unmap != it->second.size) {
|
||||
SplitRegion(current_addr, size_to_unmap);
|
||||
it = std::prev(regions.upper_bound(current_addr));
|
||||
}
|
||||
|
||||
// Get the address and region corresponding to this range.
|
||||
auto& [base, region] = *it;
|
||||
|
||||
// Unmap the region if it was previously mapped
|
||||
if (region.is_mapped) {
|
||||
UnmapRegion(®ion);
|
||||
}
|
||||
|
||||
// Update region data
|
||||
region.is_mapped = false;
|
||||
region.fd = -1;
|
||||
region.phys_base = -1;
|
||||
region.prot = PAGE_NOACCESS;
|
||||
|
||||
// Update loop variables
|
||||
remaining_size -= size_to_unmap;
|
||||
current_addr += size_to_unmap;
|
||||
}
|
||||
|
||||
// Coalesce any free space produced from these unmaps.
|
||||
CoalesceFreeRegions(virtual_addr);
|
||||
}
|
||||
|
||||
void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) {
|
||||
DWORD new_flags{};
|
||||
|
||||
if (write && !read) {
|
||||
@ -416,7 +511,7 @@ struct AddressSpace::Impl {
|
||||
|
||||
// If no flags are assigned, then something's gone wrong.
|
||||
if (new_flags == 0) {
|
||||
LOG_CRITICAL(Common_Memory,
|
||||
LOG_CRITICAL(Core,
|
||||
"Unsupported protection flag combination for address {:#x}, size {}, "
|
||||
"read={}, write={}, execute={}",
|
||||
virtual_addr, size, read, write, execute);
|
||||
@ -425,13 +520,14 @@ struct AddressSpace::Impl {
|
||||
|
||||
const VAddr virtual_end = virtual_addr + size;
|
||||
auto it = --regions.upper_bound(virtual_addr);
|
||||
ASSERT_MSG(it != regions.end(), "addr {:#x} out of bounds", virtual_addr);
|
||||
for (; it->first < virtual_end; it++) {
|
||||
if (!it->second.is_mapped) {
|
||||
continue;
|
||||
}
|
||||
const auto& region = it->second;
|
||||
const size_t range_addr = std::max(region.base, virtual_addr);
|
||||
const size_t range_size = std::min(region.base + region.size, virtual_end) - range_addr;
|
||||
const u64 range_addr = std::max(region.base, virtual_addr);
|
||||
const u64 range_size = std::min(region.base + region.size, virtual_end) - range_addr;
|
||||
DWORD old_flags{};
|
||||
if (!VirtualProtectEx(process, LPVOID(range_addr), range_size, new_flags, &old_flags)) {
|
||||
UNREACHABLE_MSG(
|
||||
@ -454,11 +550,11 @@ struct AddressSpace::Impl {
|
||||
u8* backing_base{};
|
||||
u8* virtual_base{};
|
||||
u8* system_managed_base{};
|
||||
size_t system_managed_size{};
|
||||
u64 system_managed_size{};
|
||||
u8* system_reserved_base{};
|
||||
size_t system_reserved_size{};
|
||||
u64 system_reserved_size{};
|
||||
u8* user_base{};
|
||||
size_t user_size{};
|
||||
u64 user_size{};
|
||||
std::map<VAddr, MemoryRegion> regions;
|
||||
};
|
||||
#else
|
||||
@ -602,7 +698,7 @@ struct AddressSpace::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, size_t size, PosixPageProtection prot,
|
||||
void* Map(VAddr virtual_addr, PAddr phys_addr, u64 size, PosixPageProtection prot,
|
||||
int fd = -1) {
|
||||
m_free_regions.subtract({virtual_addr, virtual_addr + size});
|
||||
const int handle = phys_addr != -1 ? (fd == -1 ? backing_fd : fd) : -1;
|
||||
@ -614,10 +710,10 @@ struct AddressSpace::Impl {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Unmap(VAddr virtual_addr, size_t size, bool) {
|
||||
void Unmap(VAddr virtual_addr, u64 size, bool) {
|
||||
// Check to see if we are adjacent to any regions.
|
||||
auto start_address = virtual_addr;
|
||||
auto end_address = start_address + size;
|
||||
VAddr start_address = virtual_addr;
|
||||
VAddr end_address = start_address + size;
|
||||
auto it = m_free_regions.find({start_address - 1, end_address + 1});
|
||||
|
||||
// If we are, join with them, ensuring we stay in bounds.
|
||||
@ -635,7 +731,7 @@ struct AddressSpace::Impl {
|
||||
ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
|
||||
}
|
||||
|
||||
void Protect(VAddr virtual_addr, size_t size, bool read, bool write, bool execute) {
|
||||
void Protect(VAddr virtual_addr, u64 size, bool read, bool write, bool execute) {
|
||||
int flags = PROT_NONE;
|
||||
if (read) {
|
||||
flags |= PROT_READ;
|
||||
@ -655,11 +751,11 @@ struct AddressSpace::Impl {
|
||||
int backing_fd;
|
||||
u8* backing_base{};
|
||||
u8* system_managed_base{};
|
||||
size_t system_managed_size{};
|
||||
u64 system_managed_size{};
|
||||
u8* system_reserved_base{};
|
||||
size_t system_reserved_size{};
|
||||
u64 system_reserved_size{};
|
||||
u8* user_base{};
|
||||
size_t user_size{};
|
||||
u64 user_size{};
|
||||
boost::icl::interval_set<VAddr> m_free_regions;
|
||||
};
|
||||
#endif
|
||||
@ -676,8 +772,7 @@ AddressSpace::AddressSpace() : impl{std::make_unique<Impl>()} {
|
||||
|
||||
AddressSpace::~AddressSpace() = default;
|
||||
|
||||
void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr phys_addr,
|
||||
bool is_exec) {
|
||||
void* AddressSpace::Map(VAddr virtual_addr, u64 size, PAddr phys_addr, bool is_exec) {
|
||||
#if ARCH_X86_64
|
||||
const auto prot = is_exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
#else
|
||||
@ -688,8 +783,7 @@ void* AddressSpace::Map(VAddr virtual_addr, size_t size, u64 alignment, PAddr ph
|
||||
return impl->Map(virtual_addr, phys_addr, size, prot);
|
||||
}
|
||||
|
||||
void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot,
|
||||
uintptr_t fd) {
|
||||
void* AddressSpace::MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd) {
|
||||
#ifdef _WIN32
|
||||
return impl->Map(virtual_addr, offset, size,
|
||||
ToWindowsProt(std::bit_cast<Core::MemoryProt>(prot)), fd);
|
||||
@ -699,31 +793,15 @@ void* AddressSpace::MapFile(VAddr virtual_addr, size_t size, size_t offset, u32
|
||||
#endif
|
||||
}
|
||||
|
||||
void AddressSpace::Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
|
||||
PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file) {
|
||||
void AddressSpace::Unmap(VAddr virtual_addr, u64 size, bool has_backing) {
|
||||
#ifdef _WIN32
|
||||
// There does not appear to be comparable support for partial unmapping on Windows.
|
||||
// Unfortunately, a least one title was found to require this. The workaround is to unmap
|
||||
// the entire allocation and remap the portions outside of the requested unmapping range.
|
||||
impl->Unmap(virtual_addr, size, has_backing && !readonly_file);
|
||||
|
||||
// TODO: Determine if any titles require partial unmapping support for un-backed allocations.
|
||||
ASSERT_MSG(has_backing || (start_in_vma == 0 && end_in_vma == size),
|
||||
"Partial unmapping of un-backed allocations is not supported");
|
||||
|
||||
if (start_in_vma != 0) {
|
||||
Map(virtual_addr, start_in_vma, 0, phys_base, is_exec);
|
||||
}
|
||||
|
||||
if (end_in_vma != size) {
|
||||
Map(virtual_addr + end_in_vma, size - end_in_vma, 0, phys_base + end_in_vma, is_exec);
|
||||
}
|
||||
impl->Unmap(virtual_addr, size);
|
||||
#else
|
||||
impl->Unmap(virtual_addr + start_in_vma, end_in_vma - start_in_vma, has_backing);
|
||||
impl->Unmap(virtual_addr, size, has_backing);
|
||||
#endif
|
||||
}
|
||||
|
||||
void AddressSpace::Protect(VAddr virtual_addr, size_t size, MemoryPermission perms) {
|
||||
void AddressSpace::Protect(VAddr virtual_addr, u64 size, MemoryPermission perms) {
|
||||
const bool read = True(perms & MemoryPermission::Read);
|
||||
const bool write = True(perms & MemoryPermission::Write);
|
||||
const bool execute = True(perms & MemoryPermission::Execute);
|
||||
|
||||
@ -39,7 +39,7 @@ public:
|
||||
[[nodiscard]] const u8* SystemManagedVirtualBase() const noexcept {
|
||||
return system_managed_base;
|
||||
}
|
||||
[[nodiscard]] size_t SystemManagedVirtualSize() const noexcept {
|
||||
[[nodiscard]] u64 SystemManagedVirtualSize() const noexcept {
|
||||
return system_managed_size;
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ public:
|
||||
[[nodiscard]] const u8* SystemReservedVirtualBase() const noexcept {
|
||||
return system_reserved_base;
|
||||
}
|
||||
[[nodiscard]] size_t SystemReservedVirtualSize() const noexcept {
|
||||
[[nodiscard]] u64 SystemReservedVirtualSize() const noexcept {
|
||||
return system_reserved_size;
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ public:
|
||||
[[nodiscard]] const u8* UserVirtualBase() const noexcept {
|
||||
return user_base;
|
||||
}
|
||||
[[nodiscard]] size_t UserVirtualSize() const noexcept {
|
||||
[[nodiscard]] u64 UserVirtualSize() const noexcept {
|
||||
return user_size;
|
||||
}
|
||||
|
||||
@ -73,17 +73,15 @@ public:
|
||||
* If zero is provided the mapping is considered as private.
|
||||
* @return A pointer to the mapped memory.
|
||||
*/
|
||||
void* Map(VAddr virtual_addr, size_t size, u64 alignment = 0, PAddr phys_addr = -1,
|
||||
bool exec = false);
|
||||
void* Map(VAddr virtual_addr, u64 size, PAddr phys_addr = -1, bool exec = false);
|
||||
|
||||
/// Memory maps a specified file descriptor.
|
||||
void* MapFile(VAddr virtual_addr, size_t size, size_t offset, u32 prot, uintptr_t fd);
|
||||
void* MapFile(VAddr virtual_addr, u64 size, u64 offset, u32 prot, uintptr_t fd);
|
||||
|
||||
/// Unmaps specified virtual memory area.
|
||||
void Unmap(VAddr virtual_addr, size_t size, VAddr start_in_vma, VAddr end_in_vma,
|
||||
PAddr phys_base, bool is_exec, bool has_backing, bool readonly_file);
|
||||
void Unmap(VAddr virtual_addr, u64 size, bool has_backing);
|
||||
|
||||
void Protect(VAddr virtual_addr, size_t size, MemoryPermission perms);
|
||||
void Protect(VAddr virtual_addr, u64 size, MemoryPermission perms);
|
||||
|
||||
// Returns an interval set containing all usable regions.
|
||||
boost::icl::interval_set<VAddr> GetUsableRegions();
|
||||
@ -93,11 +91,11 @@ private:
|
||||
std::unique_ptr<Impl> impl;
|
||||
u8* backing_base{};
|
||||
u8* system_managed_base{};
|
||||
size_t system_managed_size{};
|
||||
u64 system_managed_size{};
|
||||
u8* system_reserved_base{};
|
||||
size_t system_reserved_size{};
|
||||
u64 system_reserved_size{};
|
||||
u8* user_base{};
|
||||
size_t user_size{};
|
||||
u64 user_size{};
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@ -32,7 +32,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
||||
TableNextColumn();
|
||||
Text("%s", magic_enum::enum_name(m.prot).data());
|
||||
TableNextColumn();
|
||||
if (m.is_exec) {
|
||||
if (True(m.prot & MemoryProt::CpuExec)) {
|
||||
Text("X");
|
||||
}
|
||||
TableNextColumn();
|
||||
@ -44,7 +44,7 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
||||
return false;
|
||||
}
|
||||
auto m = dmem.it->second;
|
||||
if (m.dma_type == DMAType::Free) {
|
||||
if (m.dma_type == PhysicalMemoryType::Free) {
|
||||
++dmem.it;
|
||||
return DrawLine();
|
||||
}
|
||||
@ -56,7 +56,8 @@ bool MemoryMapViewer::Iterator::DrawLine() {
|
||||
auto type = static_cast<::Libraries::Kernel::MemoryTypes>(m.memory_type);
|
||||
Text("%s", magic_enum::enum_name(type).data());
|
||||
TableNextColumn();
|
||||
Text("%d", m.dma_type == DMAType::Pooled || m.dma_type == DMAType::Committed);
|
||||
Text("%d",
|
||||
m.dma_type == PhysicalMemoryType::Pooled || m.dma_type == PhysicalMemoryType::Committed);
|
||||
++dmem.it;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -11,8 +11,8 @@ class MemoryMapViewer {
|
||||
struct Iterator {
|
||||
bool is_vma;
|
||||
struct {
|
||||
MemoryManager::DMemMap::iterator it;
|
||||
MemoryManager::DMemMap::iterator end;
|
||||
MemoryManager::PhysMap::iterator it;
|
||||
MemoryManager::PhysMap::iterator end;
|
||||
} dmem;
|
||||
struct {
|
||||
MemoryManager::VMAMap::iterator it;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cmath>
|
||||
@ -346,7 +346,7 @@ int PS4_SYSV_ABI sceAppContentInitialize(const OrbisAppContentInitParam* initPar
|
||||
if (addcont_count > 0) {
|
||||
SystemService::OrbisSystemServiceEvent event{};
|
||||
event.event_type = SystemService::OrbisSystemServiceEventType::EntitlementUpdate;
|
||||
event.service_entitlement_update.user_id = 0;
|
||||
event.service_entitlement_update.userId = 0;
|
||||
event.service_entitlement_update.np_service_label = 0;
|
||||
SystemService::PushSystemServiceEvent(event);
|
||||
}
|
||||
|
||||
@ -29,10 +29,10 @@ s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(const s32 handle) {
|
||||
return AudioOut::sceAudioOutClose(handle);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceAudio3dAudioOutOpen(const OrbisAudio3dPortId port_id, const OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(
|
||||
const OrbisAudio3dPortId port_id, const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
s32 type, const s32 index, const u32 len, const u32 freq,
|
||||
const AudioOut::OrbisAudioOutParamExtendedInformation param) {
|
||||
LOG_INFO(Lib_Audio3d,
|
||||
"called, port_id = {}, user_id = {}, type = {}, index = {}, len = {}, freq = {}",
|
||||
port_id, user_id, type, index, len, freq);
|
||||
@ -422,7 +422,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetStatus() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const OrbisUserServiceUserId user_id,
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(const Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
const OrbisAudio3dOpenParameters* parameters,
|
||||
OrbisAudio3dPortId* port_id) {
|
||||
LOG_INFO(Lib_Audio3d, "called, user_id = {}, parameters = {}, id = {}", user_id,
|
||||
|
||||
@ -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
|
||||
|
||||
#pragma once
|
||||
@ -15,8 +15,6 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::Audio3d {
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
enum class OrbisAudio3dRate : u32 {
|
||||
ORBIS_AUDIO3D_RATE_48000 = 0,
|
||||
};
|
||||
@ -91,7 +89,8 @@ struct Audio3dState {
|
||||
};
|
||||
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutClose(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id, OrbisUserServiceUserId user_id,
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOpen(OrbisAudio3dPortId port_id,
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
s32 type, s32 index, u32 len, u32 freq,
|
||||
AudioOut::OrbisAudioOutParamExtendedInformation param);
|
||||
s32 PS4_SYSV_ABI sceAudio3dAudioOutOutput(s32 handle, void* ptr);
|
||||
@ -127,7 +126,7 @@ s32 PS4_SYSV_ABI sceAudio3dPortGetQueueLevel(OrbisAudio3dPortId port_id, u32* qu
|
||||
u32* queue_available);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetState();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortGetStatus();
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(OrbisUserServiceUserId user_id,
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortOpen(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
const OrbisAudio3dOpenParameters* parameters,
|
||||
OrbisAudio3dPortId* port_id);
|
||||
s32 PS4_SYSV_ABI sceAudio3dPortPush(OrbisAudio3dPortId port_id, OrbisAudio3dBlocking blocking);
|
||||
|
||||
@ -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 "common/logging/log.h"
|
||||
@ -16,7 +16,7 @@ s32 PS4_SYSV_ABI sceCompanionHttpdAddHeader(const char* key, const char* value,
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI
|
||||
sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId) {
|
||||
sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId userId) {
|
||||
LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
@ -15,11 +16,11 @@ struct OrbisGameLiveStreamingStatus {
|
||||
bool isOnAir;
|
||||
u8 align[3];
|
||||
u32 spectatorCounts;
|
||||
s32 userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
u8 reserved[60];
|
||||
};
|
||||
struct OrbisGameLiveStreamingStatus2 {
|
||||
s32 userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
bool isOnAir;
|
||||
u8 align[3];
|
||||
u32 spectatorCounts;
|
||||
|
||||
@ -89,21 +89,31 @@ s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 m
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len) {
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start,
|
||||
len);
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->Free(start, len);
|
||||
return ORBIS_OK;
|
||||
return memory->Free(start, len, true);
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) {
|
||||
LOG_INFO(Kernel_Vmm, "called start = {:#x}, len = {:#x}", start, len);
|
||||
if (!Common::Is16KBAligned(start) || !Common::Is16KBAligned(len)) {
|
||||
LOG_ERROR(Kernel_Vmm, "Misaligned start or length, start = {:#x}, length = {:#x}", start,
|
||||
len);
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
}
|
||||
if (len == 0) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->Free(start, len);
|
||||
memory->Free(start, len, false);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/np/np_types.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
@ -31,7 +32,7 @@ struct OrbisNpAuthGetAuthorizationCodeParameter {
|
||||
|
||||
struct OrbisNpAuthGetAuthorizationCodeParameterA {
|
||||
u64 size;
|
||||
s32 user_id;
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id;
|
||||
u8 padding[4];
|
||||
const OrbisNpClientId* client_id;
|
||||
const char* scope;
|
||||
@ -47,7 +48,7 @@ struct OrbisNpAuthGetIdTokenParameter {
|
||||
|
||||
struct OrbisNpAuthGetIdTokenParameterA {
|
||||
u64 size;
|
||||
s32 user_id;
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id;
|
||||
u8 padding[4];
|
||||
const OrbisNpClientId* client_id;
|
||||
const OrbisNpClientSecret* client_secret;
|
||||
|
||||
@ -23,8 +23,8 @@ enum class OrbisNpState : u32 {
|
||||
SignedIn = 2,
|
||||
};
|
||||
|
||||
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(s32 userId, OrbisNpState state,
|
||||
void* userdata);
|
||||
using OrbisNpStateCallbackForNpToolkit = PS4_SYSV_ABI void (*)(
|
||||
Libraries::UserService::OrbisUserServiceUserId userId, OrbisNpState state, void* userdata);
|
||||
|
||||
enum class OrbisNpGamePresenseStatus {
|
||||
Offline = 0,
|
||||
|
||||
@ -149,7 +149,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, s32 user_id,
|
||||
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
uint32_t service_label, u64 options) {
|
||||
ASSERT(options == 0ull);
|
||||
if (!context) {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/rtc/rtc.h"
|
||||
|
||||
@ -132,7 +133,8 @@ int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup();
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion();
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails();
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature();
|
||||
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, s32 user_id,
|
||||
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
u32 service_label, u64 options);
|
||||
s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle);
|
||||
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context);
|
||||
|
||||
@ -162,7 +162,8 @@ int PS4_SYSV_ABI scePadGetFeatureReport() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadGetHandle(s32 userId, s32 type, s32 index) {
|
||||
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index) {
|
||||
if (!g_initialized) {
|
||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
@ -259,7 +260,8 @@ int PS4_SYSV_ABI scePadMbusTerm() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam) {
|
||||
int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index, const OrbisPadOpenParam* pParam) {
|
||||
if (!g_initialized) {
|
||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
@ -280,8 +282,8 @@ int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenP
|
||||
return userId; // TODO: userId shouldn't be used as the handle too
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index,
|
||||
const OrbisPadOpenExtParam* pParam) {
|
||||
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index, const OrbisPadOpenExtParam* pParam) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
if (EmulatorSettings::GetInstance()->IsUsingSpecialPad()) {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
|
||||
@ -276,7 +277,8 @@ int PS4_SYSV_ABI scePadGetExtControllerInformation(s32 handle,
|
||||
OrbisPadExtendedControllerInformation* pInfo);
|
||||
int PS4_SYSV_ABI scePadGetExtensionUnitInfo();
|
||||
int PS4_SYSV_ABI scePadGetFeatureReport();
|
||||
int PS4_SYSV_ABI scePadGetHandle(s32 userId, s32 type, s32 index);
|
||||
int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index);
|
||||
int PS4_SYSV_ABI scePadGetIdleCount();
|
||||
int PS4_SYSV_ABI scePadGetInfo();
|
||||
int PS4_SYSV_ABI scePadGetInfoByPortType();
|
||||
@ -294,8 +296,10 @@ int PS4_SYSV_ABI scePadIsMoveReproductionModel();
|
||||
int PS4_SYSV_ABI scePadIsValidHandle();
|
||||
int PS4_SYSV_ABI scePadMbusInit();
|
||||
int PS4_SYSV_ABI scePadMbusTerm();
|
||||
int PS4_SYSV_ABI scePadOpen(s32 userId, s32 type, s32 index, const OrbisPadOpenParam* pParam);
|
||||
int PS4_SYSV_ABI scePadOpenExt(s32 userId, s32 type, s32 index, const OrbisPadOpenExtParam* pParam);
|
||||
int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index, const OrbisPadOpenParam* pParam);
|
||||
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index, const OrbisPadOpenExtParam* pParam);
|
||||
int PS4_SYSV_ABI scePadOpenExt2();
|
||||
int PS4_SYSV_ABI scePadOutputReport();
|
||||
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num);
|
||||
|
||||
@ -54,7 +54,8 @@ int PS4_SYSV_ABI sceRemoteplayGetConnectHistory() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus) {
|
||||
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(
|
||||
Libraries::UserService::OrbisUserServiceUserId userId, int* pStatus) {
|
||||
*pStatus = ORBIS_REMOTEPLAY_CONNECTION_STATUS_DISCONNECT;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
@ -24,7 +25,8 @@ int PS4_SYSV_ABI sceRemoteplayDisconnect();
|
||||
int PS4_SYSV_ABI sceRemoteplayGeneratePinCode();
|
||||
int PS4_SYSV_ABI sceRemoteplayGetApMode();
|
||||
int PS4_SYSV_ABI sceRemoteplayGetConnectHistory();
|
||||
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(s32 userId, int* pStatus);
|
||||
int PS4_SYSV_ABI sceRemoteplayGetConnectionStatus(
|
||||
Libraries::UserService::OrbisUserServiceUserId userId, int* pStatus);
|
||||
int PS4_SYSV_ABI sceRemoteplayGetConnectUserId();
|
||||
int PS4_SYSV_ABI sceRemoteplayGetMbusDeviceInfo();
|
||||
int PS4_SYSV_ABI sceRemoteplayGetOperationStatus();
|
||||
|
||||
@ -167,7 +167,7 @@ void StopThread() {
|
||||
g_backup_thread_semaphore.release();
|
||||
}
|
||||
|
||||
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
|
||||
bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view title_id,
|
||||
std::string_view dir_name, OrbisSaveDataEventType origin) {
|
||||
auto save_path = SaveInstance::MakeDirSavePath(user_id, title_id, dir_name);
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
#pragma once
|
||||
@ -6,12 +6,11 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
namespace Backup {
|
||||
|
||||
enum class WorkerStatus {
|
||||
@ -32,7 +31,7 @@ enum class OrbisSaveDataEventType : u32 {
|
||||
struct BackupRequest {
|
||||
bool done{};
|
||||
|
||||
OrbisUserServiceUserId user_id{};
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id{};
|
||||
std::string title_id{};
|
||||
std::string dir_name{};
|
||||
OrbisSaveDataEventType origin{};
|
||||
@ -45,7 +44,7 @@ void StartThread();
|
||||
|
||||
void StopThread();
|
||||
|
||||
bool NewRequest(OrbisUserServiceUserId user_id, std::string_view title_id,
|
||||
bool NewRequest(Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view title_id,
|
||||
std::string_view dir_name, OrbisSaveDataEventType origin);
|
||||
|
||||
bool Restore(const std::filesystem::path& save_path);
|
||||
|
||||
@ -47,7 +47,7 @@ static const std::unordered_map<int, std::string> default_title = {
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
|
||||
fs::path SaveInstance::MakeTitleSavePath(OrbisUserServiceUserId user_id,
|
||||
fs::path SaveInstance::MakeTitleSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial) {
|
||||
return EmulatorSettings::GetInstance()->GetHomeDir() / std::to_string(user_id) / "savedata" /
|
||||
game_serial;
|
||||
@ -92,8 +92,8 @@ void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
|
||||
#undef P
|
||||
}
|
||||
|
||||
SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial,
|
||||
std::string_view _dir_name, int max_blocks)
|
||||
SaveInstance::SaveInstance(int slot_num, Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
std::string _game_serial, std::string_view _dir_name, int max_blocks)
|
||||
: slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)),
|
||||
dir_name(_dir_name),
|
||||
max_blocks(std::clamp(max_blocks, OrbisSaveDataBlocksMin2, OrbisSaveDataBlocksMax)) {
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/io_file.h"
|
||||
#include "core/file_format/psf.h"
|
||||
|
||||
@ -52,13 +53,13 @@ class SaveInstance {
|
||||
|
||||
public:
|
||||
// Location of all save data for a title
|
||||
static std::filesystem::path MakeTitleSavePath(OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial);
|
||||
static std::filesystem::path MakeTitleSavePath(
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial);
|
||||
|
||||
// Location of a specific save data directory
|
||||
static std::filesystem::path MakeDirSavePath(OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial,
|
||||
std::string_view dir_name);
|
||||
static std::filesystem::path MakeDirSavePath(
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id, std::string_view game_serial,
|
||||
std::string_view dir_name);
|
||||
|
||||
static uint64_t GetMaxBlockFromSFO(const PSF& psf);
|
||||
|
||||
@ -67,8 +68,8 @@ public:
|
||||
|
||||
static void SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial);
|
||||
|
||||
explicit SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string game_serial,
|
||||
std::string_view dir_name, int max_blocks = 0);
|
||||
explicit SaveInstance(int slot_num, Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
std::string game_serial, std::string_view dir_name, int max_blocks = 0);
|
||||
|
||||
~SaveInstance();
|
||||
|
||||
|
||||
@ -88,8 +88,8 @@ std::string GetSaveDir(u32 slot_id) {
|
||||
return dir;
|
||||
}
|
||||
|
||||
std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
|
||||
std::string_view game_serial) {
|
||||
std::filesystem::path GetSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
u32 slot_id, std::string_view game_serial) {
|
||||
std::string dir(StandardDirnameSaveDataMemory);
|
||||
if (slot_id > 0) {
|
||||
dir += std::to_string(slot_id);
|
||||
@ -97,8 +97,8 @@ std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
|
||||
return SaveInstance::MakeDirSavePath(user_id, game_serial, dir);
|
||||
}
|
||||
|
||||
size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial,
|
||||
size_t memory_size) {
|
||||
size_t SetupSaveMemory(Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id,
|
||||
std::string_view game_serial, size_t memory_size) {
|
||||
std::lock_guard lck{g_slot_mtx};
|
||||
|
||||
const auto save_dir = GetSavePath(user_id, slot_id, game_serial);
|
||||
|
||||
@ -4,26 +4,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "core/libraries/save_data/save_backup.h"
|
||||
|
||||
class PSF;
|
||||
|
||||
namespace Libraries::SaveData {
|
||||
using OrbisUserServiceUserId = s32;
|
||||
} // namespace Libraries::SaveData
|
||||
|
||||
namespace Libraries::SaveData::SaveMemory {
|
||||
|
||||
void PersistMemory(u32 slot_id, bool lock = true);
|
||||
|
||||
[[nodiscard]] std::string GetSaveDir(u32 slot_id);
|
||||
|
||||
[[nodiscard]] std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id,
|
||||
std::string_view game_serial);
|
||||
[[nodiscard]] std::filesystem::path GetSavePath(
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id,
|
||||
std::string_view game_serial);
|
||||
|
||||
// returns the size of the save memory if exists
|
||||
size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial,
|
||||
size_t memory_size);
|
||||
size_t SetupSaveMemory(Libraries::UserService::OrbisUserServiceUserId user_id, u32 slot_id,
|
||||
std::string_view game_serial, size_t memory_size);
|
||||
|
||||
// Write the icon. Set buf to null to read the standard icon.
|
||||
void SetIcon(u32 slot_id, void* buf = nullptr, size_t buf_size = 0);
|
||||
|
||||
@ -44,7 +44,6 @@ enum class OrbisSaveDataSaveDataMemoryOption : u32 {
|
||||
UNLOCK_LIMITATIONS = 1 << 2,
|
||||
};
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
using OrbisSaveDataBlocks = u64;
|
||||
|
||||
constexpr u32 OrbisSaveDataBlockSize = 32768; // 32 KiB
|
||||
@ -99,7 +98,7 @@ struct OrbisSaveDataFingerprint {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataBackup {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
@ -108,7 +107,7 @@ struct OrbisSaveDataBackup {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataCheckBackupData {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
@ -118,7 +117,7 @@ struct OrbisSaveDataCheckBackupData {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDelete {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
@ -155,7 +154,7 @@ struct OrbisSaveDataMemoryData {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemoryGet2 {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
std::array<u8, 4> _pad;
|
||||
OrbisSaveDataMemoryData* data;
|
||||
OrbisSaveDataParam* param;
|
||||
@ -165,7 +164,7 @@ struct OrbisSaveDataMemoryGet2 {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemorySet2 {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
std::array<u8, 4> _pad;
|
||||
const OrbisSaveDataMemoryData* data;
|
||||
const OrbisSaveDataParam* param;
|
||||
@ -177,7 +176,7 @@ struct OrbisSaveDataMemorySet2 {
|
||||
|
||||
struct OrbisSaveDataMemorySetup2 {
|
||||
OrbisSaveDataSaveDataMemoryOption option;
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
size_t memorySize;
|
||||
size_t iconMemorySize;
|
||||
// +4.5
|
||||
@ -199,14 +198,14 @@ enum OrbisSaveDataMemorySyncOption : u32 {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMemorySync {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
u32 slotId;
|
||||
OrbisSaveDataMemorySyncOption option;
|
||||
std::array<u8, 28> _reserved;
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMount2 {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
OrbisSaveDataBlocks blocks;
|
||||
@ -216,7 +215,7 @@ struct OrbisSaveDataMount2 {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataMount {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
@ -247,7 +246,7 @@ struct OrbisSaveDataMountResult {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataRestoreBackupData {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
s32 : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
@ -258,7 +257,7 @@ struct OrbisSaveDataRestoreBackupData {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataTransferringMount {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
const OrbisSaveDataFingerprint* fingerprint;
|
||||
@ -266,7 +265,7 @@ struct OrbisSaveDataTransferringMount {
|
||||
};
|
||||
|
||||
struct OrbisSaveDataDirNameSearchCond {
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
int : 32;
|
||||
const OrbisSaveDataTitleId* titleId;
|
||||
const OrbisSaveDataDirName* dirName;
|
||||
@ -305,7 +304,7 @@ using OrbisSaveDataEventType = Backup::OrbisSaveDataEventType;
|
||||
struct OrbisSaveDataEvent {
|
||||
OrbisSaveDataEventType type;
|
||||
s32 errorCode;
|
||||
OrbisUserServiceUserId userId;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
std::array<u8, 4> _pad;
|
||||
OrbisSaveDataTitleId titleId;
|
||||
OrbisSaveDataDirName dirName;
|
||||
@ -1111,8 +1110,9 @@ int PS4_SYSV_ABI sceSaveDataGetSaveDataCount() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(const OrbisUserServiceUserId userId, void* buf,
|
||||
const size_t bufSize, const int64_t offset) {
|
||||
Error PS4_SYSV_ABI
|
||||
sceSaveDataGetSaveDataMemory(const Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
|
||||
const size_t bufSize, const int64_t offset) {
|
||||
LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataGetSaveDataMemory2");
|
||||
OrbisSaveDataMemoryData data{};
|
||||
data.buf = buf;
|
||||
@ -1474,8 +1474,9 @@ int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset) {
|
||||
Error PS4_SYSV_ABI
|
||||
sceSaveDataSetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset) {
|
||||
LOG_DEBUG(Lib_SaveData, "Redirecting to sceSaveDataSetSaveDataMemory2");
|
||||
OrbisSaveDataMemoryData data{};
|
||||
data.buf = buf;
|
||||
@ -1532,8 +1533,9 @@ Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2*
|
||||
return Error::OK;
|
||||
}
|
||||
|
||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
|
||||
OrbisSaveDataParam* param) {
|
||||
Error PS4_SYSV_ABI
|
||||
sceSaveDataSetupSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
size_t memorySize, OrbisSaveDataParam* param) {
|
||||
LOG_DEBUG(Lib_SaveData, "called: userId = {}, memorySize = {}", userId, memorySize);
|
||||
OrbisSaveDataMemorySetup2 setupParam{};
|
||||
setupParam.userId = userId;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/cstring.h"
|
||||
#include "common/types.h"
|
||||
|
||||
@ -21,8 +22,6 @@ constexpr size_t OrbisSaveDataDetailMaxsize = 1024; // Maximum detail name size
|
||||
enum class Error : u32;
|
||||
enum class OrbisSaveDataParamType : u32;
|
||||
|
||||
using OrbisUserServiceUserId = s32;
|
||||
|
||||
// Maximum size for a title ID (4 uppercase letters + 5 digits)
|
||||
constexpr int OrbisSaveDataTitleIdDataSize = 10;
|
||||
// Maximum save directory name size
|
||||
@ -126,8 +125,9 @@ Error PS4_SYSV_ABI sceSaveDataGetParam(const OrbisSaveDataMountPoint* mountPoint
|
||||
size_t paramBufSize, size_t* gotSize);
|
||||
Error PS4_SYSV_ABI sceSaveDataGetProgress(float* progress);
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataCount();
|
||||
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset);
|
||||
Error PS4_SYSV_ABI
|
||||
sceSaveDataGetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset);
|
||||
Error PS4_SYSV_ABI sceSaveDataGetSaveDataMemory2(OrbisSaveDataMemoryGet2* getParam);
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootDir();
|
||||
int PS4_SYSV_ABI sceSaveDataGetSaveDataRootPath();
|
||||
@ -163,11 +163,13 @@ Error PS4_SYSV_ABI sceSaveDataSetParam(const OrbisSaveDataMountPoint* mountPoint
|
||||
OrbisSaveDataParamType paramType, const void* paramBuf,
|
||||
size_t paramBufSize);
|
||||
int PS4_SYSV_ABI sceSaveDataSetSaveDataLibraryUser();
|
||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory(OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset);
|
||||
Error PS4_SYSV_ABI
|
||||
sceSaveDataSetSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId, void* buf,
|
||||
size_t bufSize, int64_t offset);
|
||||
Error PS4_SYSV_ABI sceSaveDataSetSaveDataMemory2(const OrbisSaveDataMemorySet2* setParam);
|
||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory(OrbisUserServiceUserId userId, size_t memorySize,
|
||||
OrbisSaveDataParam* param);
|
||||
Error PS4_SYSV_ABI
|
||||
sceSaveDataSetupSaveDataMemory(Libraries::UserService::OrbisUserServiceUserId userId,
|
||||
size_t memorySize, OrbisSaveDataParam* param);
|
||||
Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetup2* setupParam,
|
||||
OrbisSaveDataMemorySetupResult* result);
|
||||
int PS4_SYSV_ABI sceSaveDataShutdownStart();
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/np/np_types.h"
|
||||
|
||||
@ -21,8 +22,8 @@ struct OrbisSharePlayConnectionInfo {
|
||||
int mode;
|
||||
Libraries::Np::OrbisNpOnlineId hostOnlineId;
|
||||
Libraries::Np::OrbisNpOnlineId visitorOnlineId;
|
||||
s32 hostUserId;
|
||||
s32 visitorUserId;
|
||||
Libraries::UserService::OrbisUserServiceUserId hostUserId;
|
||||
Libraries::UserService::OrbisUserServiceUserId visitorUserId;
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI sceSharePlayCrashDaemon();
|
||||
|
||||
@ -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
|
||||
// reference
|
||||
// https://github.com/OpenOrbis/OpenOrbis-PS4-Toolchain/blob/master/include/orbis/_types/sys_service.h
|
||||
@ -7,6 +7,7 @@
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include "common/types.h"
|
||||
#include "userservice.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
class SymbolsResolver;
|
||||
@ -119,12 +120,12 @@ struct OrbisSystemServiceEvent {
|
||||
char boot_argument[7169];
|
||||
} join_event;
|
||||
struct {
|
||||
s32 user_id;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
u32 np_service_label;
|
||||
u8 reserved[8184];
|
||||
} service_entitlement_update;
|
||||
struct {
|
||||
s32 user_id;
|
||||
Libraries::UserService::OrbisUserServiceUserId userId;
|
||||
u32 np_service_label;
|
||||
u8 reserved[8184];
|
||||
} unified_entitlement_update;
|
||||
|
||||
@ -292,8 +292,8 @@ s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutio
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
|
||||
const void* param) {
|
||||
s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType,
|
||||
s32 index, const void* param) {
|
||||
LOG_INFO(Lib_VideoOut, "called");
|
||||
ASSERT(busType == SCE_VIDEO_OUT_BUS_TYPE_MAIN);
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/libraries/system/userservice.h>
|
||||
#include "core/libraries/kernel/equeue.h"
|
||||
#include "core/libraries/videoout/buffer.h"
|
||||
|
||||
@ -12,8 +13,6 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::VideoOut {
|
||||
|
||||
using SceUserServiceUserId = s32; // TODO move it to proper place
|
||||
|
||||
// SceVideoOutBusType
|
||||
constexpr int SCE_VIDEO_OUT_BUS_TYPE_MAIN = 0; // Main output
|
||||
constexpr int SCE_VIDEO_OUT_BUS_TYPE_AUX_SOCIAL_SCREEN = 5; // Aux output for social
|
||||
@ -131,8 +130,8 @@ s32 PS4_SYSV_ABI sceVideoOutWaitVblank(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceVideoOutSubmitFlip(s32 handle, s32 bufferIndex, s32 flipMode, s64 flipArg);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetFlipStatus(s32 handle, FlipStatus* status);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status);
|
||||
s32 PS4_SYSV_ABI sceVideoOutOpen(SceUserServiceUserId userId, s32 busType, s32 index,
|
||||
const void* param);
|
||||
s32 PS4_SYSV_ABI sceVideoOutOpen(Libraries::UserService::OrbisUserServiceUserId userId, s32 busType,
|
||||
s32 index, const void* param);
|
||||
s32 PS4_SYSV_ABI sceVideoOutClose(s32 handle);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetEventId(const Kernel::SceKernelEvent* ev);
|
||||
s32 PS4_SYSV_ABI sceVideoOutGetEventData(const Kernel::SceKernelEvent* ev, s64* data);
|
||||
|
||||
@ -5,9 +5,12 @@
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/web_browser_dialog/webbrowserdialog.h"
|
||||
#include "magic_enum/magic_enum.hpp"
|
||||
|
||||
namespace Libraries::WebBrowserDialog {
|
||||
|
||||
static auto g_status = Libraries::CommonDialog::Status::NONE;
|
||||
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogClose() {
|
||||
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -23,14 +26,19 @@ s32 PS4_SYSV_ABI sceWebBrowserDialogGetResult() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogGetStatus() {
|
||||
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogGetStatus() {
|
||||
LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogInitialize() {
|
||||
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogInitialize() {
|
||||
if (CommonDialog::g_isInitialized) {
|
||||
LOG_INFO(Lib_WebBrowserDialog, "already initialized");
|
||||
return Libraries::CommonDialog::Error::ALREADY_SYSTEM_INITIALIZED;
|
||||
}
|
||||
LOG_DEBUG(Lib_WebBrowserDialog, "initialized");
|
||||
CommonDialog::g_isInitialized = true;
|
||||
return Libraries::CommonDialog::Error::OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogNavigate() {
|
||||
@ -63,14 +71,22 @@ s32 PS4_SYSV_ABI sceWebBrowserDialogSetZoom() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogTerminate() {
|
||||
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogTerminate() {
|
||||
if (g_status == Libraries::CommonDialog::Status::RUNNING) {
|
||||
LOG_ERROR(Lib_WebBrowserDialog,
|
||||
"CloseWebBrowser Dialog unimplemented"); // sceWebBrowserDialogClose();
|
||||
}
|
||||
if (g_status == Libraries::CommonDialog::Status::NONE) {
|
||||
return Libraries::CommonDialog::Error::NOT_INITIALIZED;
|
||||
}
|
||||
g_status = Libraries::CommonDialog::Status::NONE;
|
||||
CommonDialog::g_isUsed = false;
|
||||
return Libraries::CommonDialog::Error::OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus() {
|
||||
LOG_ERROR(Lib_WebBrowserDialog, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus() {
|
||||
LOG_TRACE(Lib_MsgDlg, "called status={}", magic_enum::enum_name(g_status));
|
||||
return g_status;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI Func_F2BE042771625F8C() {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/libraries/system/commondialog.h>
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core::Loader {
|
||||
@ -14,16 +15,16 @@ namespace Libraries::WebBrowserDialog {
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogClose();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogGetEvent();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogGetResult();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogGetStatus();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogInitialize();
|
||||
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogGetStatus();
|
||||
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogInitialize();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogNavigate();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogOpen();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogOpenForPredeterminedContent();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogResetCookie();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogSetCookie();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogSetZoom();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogTerminate();
|
||||
s32 PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus();
|
||||
Libraries::CommonDialog::Error PS4_SYSV_ABI sceWebBrowserDialogTerminate();
|
||||
Libraries::CommonDialog::Status PS4_SYSV_ABI sceWebBrowserDialogUpdateStatus();
|
||||
s32 PS4_SYSV_ABI Func_F2BE042771625F8C();
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "common/enum.h"
|
||||
#include "common/shared_first_mutex.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
#include "core/address_space.h"
|
||||
@ -54,12 +55,37 @@ enum class MemoryMapFlags : u32 {
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(MemoryMapFlags)
|
||||
|
||||
enum class DMAType : u32 {
|
||||
enum class PhysicalMemoryType : u32 {
|
||||
Free = 0,
|
||||
Allocated = 1,
|
||||
Mapped = 2,
|
||||
Pooled = 3,
|
||||
Committed = 4,
|
||||
Flexible = 5,
|
||||
};
|
||||
|
||||
struct PhysicalMemoryArea {
|
||||
PAddr base = 0;
|
||||
u64 size = 0;
|
||||
s32 memory_type = 0;
|
||||
PhysicalMemoryType dma_type = PhysicalMemoryType::Free;
|
||||
|
||||
PAddr GetEnd() const {
|
||||
return base + size;
|
||||
}
|
||||
|
||||
bool CanMergeWith(const PhysicalMemoryArea& next) const {
|
||||
if (base + size != next.base) {
|
||||
return false;
|
||||
}
|
||||
if (memory_type != next.memory_type) {
|
||||
return false;
|
||||
}
|
||||
if (dma_type != next.dma_type) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
enum class VMAType : u32 {
|
||||
@ -74,65 +100,24 @@ enum class VMAType : u32 {
|
||||
File = 8,
|
||||
};
|
||||
|
||||
struct DirectMemoryArea {
|
||||
PAddr base = 0;
|
||||
u64 size = 0;
|
||||
s32 memory_type = 0;
|
||||
DMAType dma_type = DMAType::Free;
|
||||
|
||||
PAddr GetEnd() const {
|
||||
return base + size;
|
||||
}
|
||||
|
||||
bool CanMergeWith(const DirectMemoryArea& next) const {
|
||||
if (base + size != next.base) {
|
||||
return false;
|
||||
}
|
||||
if (memory_type != next.memory_type) {
|
||||
return false;
|
||||
}
|
||||
if (dma_type != next.dma_type) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct FlexibleMemoryArea {
|
||||
PAddr base = 0;
|
||||
u64 size = 0;
|
||||
bool is_free = true;
|
||||
|
||||
PAddr GetEnd() const {
|
||||
return base + size;
|
||||
}
|
||||
|
||||
bool CanMergeWith(const FlexibleMemoryArea& next) const {
|
||||
if (base + size != next.base) {
|
||||
return false;
|
||||
}
|
||||
if (is_free != next.is_free) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
struct VirtualMemoryArea {
|
||||
VAddr base = 0;
|
||||
u64 size = 0;
|
||||
PAddr phys_base = 0;
|
||||
std::map<uintptr_t, PhysicalMemoryArea> phys_areas;
|
||||
VMAType type = VMAType::Free;
|
||||
MemoryProt prot = MemoryProt::NoAccess;
|
||||
bool disallow_merge = false;
|
||||
std::string name = "";
|
||||
uintptr_t fd = 0;
|
||||
bool is_exec = false;
|
||||
s32 fd = 0;
|
||||
bool disallow_merge = false;
|
||||
|
||||
bool Contains(VAddr addr, u64 size) const {
|
||||
return addr >= base && (addr + size) <= (base + this->size);
|
||||
}
|
||||
|
||||
bool Overlaps(VAddr addr, u64 size) const {
|
||||
return addr <= (base + this->size) && (addr + size) >= base;
|
||||
}
|
||||
|
||||
bool IsFree() const noexcept {
|
||||
return type == VMAType::Free;
|
||||
}
|
||||
@ -141,30 +126,35 @@ struct VirtualMemoryArea {
|
||||
return type != VMAType::Free && type != VMAType::Reserved && type != VMAType::PoolReserved;
|
||||
}
|
||||
|
||||
bool CanMergeWith(const VirtualMemoryArea& next) const {
|
||||
bool CanMergeWith(VirtualMemoryArea& next) {
|
||||
if (disallow_merge || next.disallow_merge) {
|
||||
return false;
|
||||
}
|
||||
if (base + size != next.base) {
|
||||
return false;
|
||||
}
|
||||
if ((type == VMAType::Direct || type == VMAType::Flexible || type == VMAType::Pooled) &&
|
||||
phys_base + size != next.phys_base) {
|
||||
return false;
|
||||
if (type == VMAType::Direct && next.type == VMAType::Direct) {
|
||||
auto& last_phys = std::prev(phys_areas.end())->second;
|
||||
auto& first_next_phys = next.phys_areas.begin()->second;
|
||||
if (last_phys.base + last_phys.size != first_next_phys.base ||
|
||||
last_phys.memory_type != first_next_phys.memory_type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (prot != next.prot || type != next.type) {
|
||||
return false;
|
||||
}
|
||||
if (name.compare(next.name) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class MemoryManager {
|
||||
using DMemMap = std::map<PAddr, DirectMemoryArea>;
|
||||
using DMemHandle = DMemMap::iterator;
|
||||
|
||||
using FMemMap = std::map<PAddr, FlexibleMemoryArea>;
|
||||
using FMemHandle = FMemMap::iterator;
|
||||
using PhysMap = std::map<PAddr, PhysicalMemoryArea>;
|
||||
using PhysHandle = PhysMap::iterator;
|
||||
|
||||
using VMAMap = std::map<VAddr, VirtualMemoryArea>;
|
||||
using VMAHandle = VMAMap::iterator;
|
||||
@ -220,10 +210,11 @@ public:
|
||||
// Now make sure the full address range is contained in vma_map.
|
||||
auto vma_handle = FindVMA(virtual_addr);
|
||||
auto addr_to_check = virtual_addr;
|
||||
s64 size_to_validate = size;
|
||||
u64 size_to_validate = size;
|
||||
while (vma_handle != vma_map.end() && size_to_validate > 0) {
|
||||
const auto offset_in_vma = addr_to_check - vma_handle->second.base;
|
||||
const auto size_in_vma = vma_handle->second.size - offset_in_vma;
|
||||
const auto size_in_vma =
|
||||
std::min<u64>(vma_handle->second.size - offset_in_vma, size_to_validate);
|
||||
size_to_validate -= size_in_vma;
|
||||
addr_to_check += size_in_vma;
|
||||
vma_handle++;
|
||||
@ -245,7 +236,7 @@ public:
|
||||
|
||||
void CopySparseMemory(VAddr source, u8* dest, u64 size);
|
||||
|
||||
bool TryWriteBacking(void* address, const void* data, u32 num_bytes);
|
||||
bool TryWriteBacking(void* address, const void* data, u64 size);
|
||||
|
||||
void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2);
|
||||
|
||||
@ -253,7 +244,7 @@ public:
|
||||
|
||||
PAddr Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment, s32 memory_type);
|
||||
|
||||
void Free(PAddr phys_addr, u64 size);
|
||||
s32 Free(PAddr phys_addr, u64 size, bool is_checked);
|
||||
|
||||
s32 PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot, s32 mtype);
|
||||
|
||||
@ -300,52 +291,37 @@ private:
|
||||
return std::prev(vma_map.upper_bound(target));
|
||||
}
|
||||
|
||||
DMemHandle FindDmemArea(PAddr target) {
|
||||
PhysHandle FindDmemArea(PAddr target) {
|
||||
return std::prev(dmem_map.upper_bound(target));
|
||||
}
|
||||
|
||||
FMemHandle FindFmemArea(PAddr target) {
|
||||
PhysHandle FindFmemArea(PAddr target) {
|
||||
return std::prev(fmem_map.upper_bound(target));
|
||||
}
|
||||
|
||||
template <typename Handle>
|
||||
Handle MergeAdjacent(auto& handle_map, Handle iter) {
|
||||
const auto next_vma = std::next(iter);
|
||||
if (next_vma != handle_map.end() && iter->second.CanMergeWith(next_vma->second)) {
|
||||
iter->second.size += next_vma->second.size;
|
||||
handle_map.erase(next_vma);
|
||||
}
|
||||
|
||||
if (iter != handle_map.begin()) {
|
||||
auto prev_vma = std::prev(iter);
|
||||
if (prev_vma->second.CanMergeWith(iter->second)) {
|
||||
prev_vma->second.size += iter->second.size;
|
||||
handle_map.erase(iter);
|
||||
iter = prev_vma;
|
||||
}
|
||||
}
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
bool HasPhysicalBacking(VirtualMemoryArea vma) {
|
||||
return vma.type == VMAType::Direct || vma.type == VMAType::Flexible ||
|
||||
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);
|
||||
|
||||
VAddr SearchFree(VAddr virtual_addr, u64 size, u32 alignment);
|
||||
|
||||
VMAHandle MergeAdjacent(VMAMap& map, VMAHandle iter);
|
||||
|
||||
PhysHandle MergeAdjacent(PhysMap& map, PhysHandle iter);
|
||||
|
||||
VMAHandle CarveVMA(VAddr virtual_addr, u64 size);
|
||||
|
||||
DMemHandle CarveDmemArea(PAddr addr, u64 size);
|
||||
|
||||
FMemHandle CarveFmemArea(PAddr addr, u64 size);
|
||||
PhysHandle CarvePhysArea(PhysMap& map, PAddr addr, u64 size);
|
||||
|
||||
VMAHandle Split(VMAHandle vma_handle, u64 offset_in_vma);
|
||||
|
||||
DMemHandle Split(DMemHandle dmem_handle, u64 offset_in_area);
|
||||
|
||||
FMemHandle Split(FMemHandle fmem_handle, u64 offset_in_area);
|
||||
PhysHandle Split(PhysMap& map, PhysHandle dmem_handle, u64 offset_in_area);
|
||||
|
||||
u64 UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size);
|
||||
|
||||
@ -353,14 +329,15 @@ private:
|
||||
|
||||
private:
|
||||
AddressSpace impl;
|
||||
DMemMap dmem_map;
|
||||
FMemMap fmem_map;
|
||||
PhysMap dmem_map;
|
||||
PhysMap fmem_map;
|
||||
VMAMap vma_map;
|
||||
std::mutex mutex;
|
||||
Common::SharedFirstMutex mutex{};
|
||||
u64 total_direct_size{};
|
||||
u64 total_flexible_size{};
|
||||
u64 flexible_usage{};
|
||||
u64 pool_budget{};
|
||||
s32 sdk_version{};
|
||||
Vulkan::Rasterizer* rasterizer{};
|
||||
|
||||
struct PrtArea {
|
||||
|
||||
370
src/main.cpp
370
src/main.cpp
@ -1,16 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
#include <CLI/CLI.hpp>
|
||||
#include <SDL3/SDL_messagebox.h>
|
||||
#include "functional"
|
||||
#include "iostream"
|
||||
#include "string"
|
||||
#include "system_error"
|
||||
#include "unordered_map"
|
||||
|
||||
#include <core/emulator_state.h>
|
||||
#include <fmt/core.h>
|
||||
#include "common/config.h"
|
||||
#include "common/key_manager.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/memory_patcher.h"
|
||||
#include "common/path_util.h"
|
||||
@ -29,6 +30,7 @@ int main(int argc, char* argv[]) {
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
#endif
|
||||
|
||||
IPC::Instance().Init();
|
||||
// Init emulator state
|
||||
std::shared_ptr<EmulatorState> m_emu_state = std::make_shared<EmulatorState>();
|
||||
@ -37,254 +39,166 @@ int main(int argc, char* argv[]) {
|
||||
std::shared_ptr<EmulatorSettings> emu_settings = std::make_shared<EmulatorSettings>();
|
||||
EmulatorSettings::SetInstance(emu_settings);
|
||||
emu_settings->Load();
|
||||
|
||||
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
Config::load(user_dir / "config.toml");
|
||||
// temp copy the trophy key from old config to key manager if exists
|
||||
|
||||
// ---- Trophy key migration ----
|
||||
auto key_manager = KeyManager::GetInstance();
|
||||
if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty()) {
|
||||
if (!Config::getTrophyKey().empty()) {
|
||||
|
||||
key_manager->SetAllKeys(
|
||||
{.TrophyKeySet = {.ReleaseTrophyKey =
|
||||
KeyManager::HexStringToBytes(Config::getTrophyKey())}});
|
||||
key_manager->SaveToFile();
|
||||
}
|
||||
if (key_manager->GetAllKeys().TrophyKeySet.ReleaseTrophyKey.empty() &&
|
||||
!Config::getTrophyKey().empty()) {
|
||||
key_manager->SetAllKeys({.TrophyKeySet = {.ReleaseTrophyKey = KeyManager::HexStringToBytes(
|
||||
Config::getTrophyKey())}});
|
||||
key_manager->SaveToFile();
|
||||
}
|
||||
bool has_game_argument = false;
|
||||
std::string game_path;
|
||||
std::vector<std::string> game_args{};
|
||||
std::optional<std::filesystem::path> game_folder;
|
||||
|
||||
bool waitForDebugger = false;
|
||||
CLI::App app{"shadPS4 Emulator CLI"};
|
||||
|
||||
// ---- CLI state ----
|
||||
std::optional<std::string> gamePath;
|
||||
std::vector<std::string> gameArgs;
|
||||
std::optional<std::filesystem::path> overrideRoot;
|
||||
std::optional<int> waitPid;
|
||||
bool waitForDebugger = false;
|
||||
|
||||
// Map of argument strings to lambda functions
|
||||
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
|
||||
{"-h",
|
||||
[&](int&) {
|
||||
std::cout
|
||||
<< "Usage: shadps4 [options] <elf or eboot.bin path>\n"
|
||||
"Options:\n"
|
||||
" -g, --game <path|ID> Specify game path to launch\n"
|
||||
" -- ... Parameters passed to the game ELF. "
|
||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||
"game argument.\n"
|
||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
|
||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||
"state. Does not overwrite the config file.\n"
|
||||
" --add-game-folder <folder> Adds a new game folder to the config.\n"
|
||||
" --set-addon-folder <folder> Sets the addon folder to the config.\n"
|
||||
" --log-append Append log output to file instead of "
|
||||
"overwriting it.\n"
|
||||
" --override-root <folder> Override the game root folder. Default is the "
|
||||
"parent of game path\n"
|
||||
" --wait-for-debugger Wait for debugger to attach\n"
|
||||
" --wait-for-pid <pid> Wait for process with specified PID to stop\n"
|
||||
" --config-clean Run the emulator with the default config "
|
||||
"values, ignores the config file(s) entirely.\n"
|
||||
" --config-global Run the emulator with the base config file "
|
||||
"only, ignores game specific configs.\n"
|
||||
" --show-fps Enable FPS counter display at startup\n"
|
||||
" -h, --help Display this help message\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--help", [&](int& i) { arg_map["-h"](i); }},
|
||||
std::optional<std::string> fullscreenStr;
|
||||
bool ignoreGamePatch = false;
|
||||
bool showFps = false;
|
||||
bool configClean = false;
|
||||
bool configGlobal = false;
|
||||
bool logAppend = false;
|
||||
|
||||
{"-g",
|
||||
[&](int& i) {
|
||||
if (i + 1 < argc) {
|
||||
game_path = argv[++i];
|
||||
has_game_argument = true;
|
||||
} else {
|
||||
std::cerr << "Error: Missing argument for -g/--game\n";
|
||||
exit(1);
|
||||
}
|
||||
}},
|
||||
{"--game", [&](int& i) { arg_map["-g"](i); }},
|
||||
std::optional<std::filesystem::path> addGameFolder;
|
||||
std::optional<std::filesystem::path> setAddonFolder;
|
||||
std::optional<std::string> patchFile;
|
||||
|
||||
{"-p",
|
||||
[&](int& i) {
|
||||
if (i + 1 < argc) {
|
||||
MemoryPatcher::patch_file = argv[++i];
|
||||
} else {
|
||||
std::cerr << "Error: Missing argument for -p/--patch\n";
|
||||
exit(1);
|
||||
}
|
||||
}},
|
||||
{"--patch", [&](int& i) { arg_map["-p"](i); }},
|
||||
// ---- Options ----
|
||||
app.add_option("-g,--game", gamePath, "Game path or ID");
|
||||
app.add_option("-p,--patch", patchFile, "Patch file to apply");
|
||||
app.add_flag("-i,--ignore-game-patch", ignoreGamePatch,
|
||||
"Disable automatic loading of game patches");
|
||||
|
||||
{"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }},
|
||||
{"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }},
|
||||
{"-f",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for -f/--fullscreen\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string f_param(argv[i]);
|
||||
bool is_fullscreen;
|
||||
if (f_param == "true") {
|
||||
is_fullscreen = true;
|
||||
} else if (f_param == "false") {
|
||||
is_fullscreen = false;
|
||||
} else {
|
||||
std::cerr
|
||||
<< "Error: Invalid argument for -f/--fullscreen. Use 'true' or 'false'.\n";
|
||||
exit(1);
|
||||
}
|
||||
// Set fullscreen mode without saving it to config file
|
||||
EmulatorSettings::GetInstance()->SetFullScreen(is_fullscreen);
|
||||
}},
|
||||
{"--fullscreen", [&](int& i) { arg_map["-f"](i); }},
|
||||
{"--add-game-folder",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --add-game-folder\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string config_dir(argv[i]);
|
||||
std::filesystem::path config_path = std::filesystem::path(config_dir);
|
||||
std::error_code discard;
|
||||
if (!std::filesystem::exists(config_path, discard)) {
|
||||
std::cerr << "Error: File does not exist: " << config_path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
// FULLSCREEN: behavior-identical
|
||||
app.add_option("-f,--fullscreen", fullscreenStr, "Fullscreen mode (true|false)");
|
||||
|
||||
EmulatorSettings::GetInstance()->AddGameInstallDir(config_path);
|
||||
EmulatorSettings::GetInstance()->Save();
|
||||
std::cout << "Game folder successfully saved.\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--set-addon-folder",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --add-addon-folder\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string config_dir(argv[i]);
|
||||
std::filesystem::path config_path = std::filesystem::path(config_dir);
|
||||
std::error_code discard;
|
||||
if (!std::filesystem::exists(config_path, discard)) {
|
||||
std::cerr << "Error: File does not exist: " << config_path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
app.add_option("--override-root", overrideRoot)->check(CLI::ExistingDirectory);
|
||||
|
||||
EmulatorSettings::GetInstance()->SetAddonInstallDir(config_path);
|
||||
EmulatorSettings::GetInstance()->Save();
|
||||
std::cout << "Addon folder successfully saved.\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--log-append", [&](int& i) { Common::Log::SetAppend(); }},
|
||||
{"--config-clean", [&](int& i) { Config::setConfigMode(Config::ConfigMode::Clean); }},
|
||||
{"--config-global", [&](int& i) { Config::setConfigMode(Config::ConfigMode::Global); }},
|
||||
{"--override-root",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --override-root\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string folder_str{argv[i]};
|
||||
std::filesystem::path folder{folder_str};
|
||||
if (!std::filesystem::exists(folder) || !std::filesystem::is_directory(folder)) {
|
||||
std::cerr << "Error: Folder does not exist: " << folder_str << "\n";
|
||||
exit(1);
|
||||
}
|
||||
game_folder = folder;
|
||||
}},
|
||||
{"--wait-for-debugger", [&](int& i) { waitForDebugger = true; }},
|
||||
{"--wait-for-pid",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --wait-for-pid\n";
|
||||
exit(1);
|
||||
}
|
||||
waitPid = std::stoi(argv[i]);
|
||||
}},
|
||||
{"--show-fps", [&](int& i) { EmulatorSettings::GetInstance()->SetShowFpsCounter(true); }}};
|
||||
app.add_flag("--wait-for-debugger", waitForDebugger);
|
||||
app.add_option("--wait-for-pid", waitPid);
|
||||
|
||||
app.add_flag("--show-fps", showFps);
|
||||
app.add_flag("--config-clean", configClean);
|
||||
app.add_flag("--config-global", configGlobal);
|
||||
app.add_flag("--log-append", logAppend);
|
||||
|
||||
app.add_option("--add-game-folder", addGameFolder)->check(CLI::ExistingDirectory);
|
||||
app.add_option("--set-addon-folder", setAddonFolder)->check(CLI::ExistingDirectory);
|
||||
|
||||
// ---- Capture args after `--` verbatim ----
|
||||
app.allow_extras();
|
||||
app.parse_complete_callback([&]() {
|
||||
const auto& extras = app.remaining();
|
||||
if (!extras.empty()) {
|
||||
gameArgs = extras;
|
||||
}
|
||||
});
|
||||
|
||||
// ---- No-args behavior ----
|
||||
if (argc == 1) {
|
||||
if (!SDL_ShowSimpleMessageBox(
|
||||
SDL_MESSAGEBOX_INFORMATION, "shadPS4",
|
||||
"This is a CLI application. Please use the QTLauncher for a GUI: "
|
||||
"https://github.com/shadps4-emu/shadps4-qtlauncher/releases",
|
||||
nullptr))
|
||||
std::cerr << "Could not display SDL message box! Error: " << SDL_GetError() << "\n";
|
||||
int dummy = 0; // one does not simply pass 0 directly
|
||||
arg_map.at("-h")(dummy);
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "shadPS4",
|
||||
"This is a CLI application. Please use the QTLauncher for a GUI:\n"
|
||||
"https://github.com/shadps4-emu/shadps4-qtlauncher/releases",
|
||||
nullptr);
|
||||
std::cout << app.help();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parse command-line arguments using the map
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string cur_arg = argv[i];
|
||||
auto it = arg_map.find(cur_arg);
|
||||
if (it != arg_map.end()) {
|
||||
it->second(i); // Call the associated lambda function
|
||||
} else if (i == argc - 1 && !has_game_argument) {
|
||||
// Assume the last argument is the game file if not specified via -g/--game
|
||||
game_path = argv[i];
|
||||
has_game_argument = true;
|
||||
} else if (std::string(argv[i]) == "--") {
|
||||
if (i + 1 == argc) {
|
||||
std::cerr << "Warning: -- is set, but no game arguments are added!\n";
|
||||
break;
|
||||
}
|
||||
for (int j = i + 1; j < argc; j++) {
|
||||
game_args.push_back(argv[j]);
|
||||
}
|
||||
break;
|
||||
} else if (i + 1 < argc && std::string(argv[i + 1]) == "--") {
|
||||
if (!has_game_argument) {
|
||||
game_path = argv[i];
|
||||
has_game_argument = true;
|
||||
}
|
||||
try {
|
||||
app.parse(argc, argv);
|
||||
} catch (const CLI::ParseError& e) {
|
||||
return app.exit(e);
|
||||
}
|
||||
|
||||
// ---- Utility commands ----
|
||||
if (addGameFolder) {
|
||||
Config::addGameInstallDir(*addGameFolder);
|
||||
Config::save(user_dir / "config.toml");
|
||||
std::cout << "Game folder successfully saved.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (setAddonFolder) {
|
||||
Config::setAddonInstallDir(*setAddonFolder);
|
||||
Config::save(user_dir / "config.toml");
|
||||
std::cout << "Addon folder successfully saved.\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!gamePath.has_value()) {
|
||||
if (!gameArgs.empty()) {
|
||||
gamePath = gameArgs.front();
|
||||
gameArgs.erase(gameArgs.begin());
|
||||
} else {
|
||||
std::cerr << "Unknown argument: " << cur_arg << ", see --help for info.\n";
|
||||
}
|
||||
}
|
||||
|
||||
// If no game directory is set and no command line argument, prompt for it
|
||||
if (EmulatorSettings::GetInstance()->GetGameInstallDirs().empty()) {
|
||||
std::cerr << "Warning: No game folder set, please set it by calling shadps4"
|
||||
" with the --add-game-folder <folder_name> argument\n";
|
||||
}
|
||||
|
||||
if (!has_game_argument) {
|
||||
std::cerr << "Error: Please provide a game path or ID.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Check if the game path or ID exists
|
||||
std::filesystem::path eboot_path(game_path);
|
||||
|
||||
// Check if the provided path is a valid file
|
||||
if (!std::filesystem::exists(eboot_path)) {
|
||||
// If not a file, treat it as a game ID and search in install directories recursively
|
||||
bool game_found = false;
|
||||
const int max_depth = 5;
|
||||
for (const auto& install_dir : EmulatorSettings::GetInstance()->GetGameInstallDirs()) {
|
||||
if (auto found_path = Common::FS::FindGameByID(install_dir, game_path, max_depth)) {
|
||||
eboot_path = *found_path;
|
||||
game_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!game_found) {
|
||||
std::cerr << "Error: Game ID or file path not found: " << game_path << std::endl;
|
||||
std::cerr << "Error: Please provide a game path or ID.\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitPid.has_value()) {
|
||||
Core::Debugger::WaitForPid(waitPid.value());
|
||||
// ---- Apply flags ----
|
||||
if (patchFile)
|
||||
MemoryPatcher::patch_file = *patchFile;
|
||||
|
||||
if (ignoreGamePatch)
|
||||
Core::FileSys::MntPoints::ignore_game_patches = true;
|
||||
|
||||
if (fullscreenStr) {
|
||||
if (*fullscreenStr == "true") {
|
||||
Config::setIsFullscreen(true);
|
||||
} else if (*fullscreenStr == "false") {
|
||||
Config::setIsFullscreen(false);
|
||||
} else {
|
||||
std::cerr << "Error: Invalid argument for --fullscreen (use true|false)\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Run the emulator with the resolved eboot path
|
||||
Core::Emulator* emulator = Common::Singleton<Core::Emulator>::Instance();
|
||||
if (showFps)
|
||||
Config::setShowFpsCounter(true);
|
||||
|
||||
if (configClean)
|
||||
Config::setConfigMode(Config::ConfigMode::Clean);
|
||||
|
||||
if (configGlobal)
|
||||
Config::setConfigMode(Config::ConfigMode::Global);
|
||||
|
||||
if (logAppend)
|
||||
Common::Log::SetAppend();
|
||||
|
||||
// ---- Resolve game path or ID ----
|
||||
std::filesystem::path ebootPath(*gamePath);
|
||||
if (!std::filesystem::exists(ebootPath)) {
|
||||
bool found = false;
|
||||
constexpr int maxDepth = 5;
|
||||
for (const auto& installDir : Config::getGameInstallDirs()) {
|
||||
if (auto foundPath = Common::FS::FindGameByID(installDir, *gamePath, maxDepth)) {
|
||||
ebootPath = *foundPath;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
std::cerr << "Error: Game ID or file path not found: " << *gamePath << "\n";
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (waitPid)
|
||||
Core::Debugger::WaitForPid(*waitPid);
|
||||
|
||||
auto* emulator = Common::Singleton<Core::Emulator>::Instance();
|
||||
emulator->executableName = argv[0];
|
||||
emulator->waitForDebuggerBeforeRun = waitForDebugger;
|
||||
emulator->Run(eboot_path, game_args, game_folder);
|
||||
emulator->Run(ebootPath, gameArgs, overrideRoot);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -364,7 +364,7 @@ void EmitContext::DefineInputs() {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LogicalStage::Fragment:
|
||||
case LogicalStage::Fragment: {
|
||||
if (info.loads.GetAny(IR::Attribute::FragCoord)) {
|
||||
frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input);
|
||||
}
|
||||
@ -418,7 +418,13 @@ void EmitContext::DefineInputs() {
|
||||
spv::StorageClass::Input);
|
||||
}
|
||||
}
|
||||
for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
|
||||
|
||||
const bool has_clip_distance_inputs = runtime_info.fs_info.clip_distance_emulation;
|
||||
// Clip distances attribute vector is the last in inputs array
|
||||
const auto num_inputs =
|
||||
runtime_info.fs_info.num_inputs - (has_clip_distance_inputs ? 1 : 0);
|
||||
|
||||
for (s32 i = 0; i < num_inputs; i++) {
|
||||
const auto& input = runtime_info.fs_info.inputs[i];
|
||||
if (input.IsDefault()) {
|
||||
continue;
|
||||
@ -428,12 +434,13 @@ void EmitContext::DefineInputs() {
|
||||
const auto [primary, auxiliary] = info.fs_interpolation[i];
|
||||
const Id type = F32[num_components];
|
||||
const Id attr_id = [&] {
|
||||
const auto bind_location = input.param_index + (has_clip_distance_inputs ? 1 : 0);
|
||||
if (primary == Qualifier::PerVertex &&
|
||||
profile.supports_fragment_shader_barycentric) {
|
||||
return Name(DefineInput(TypeArray(type, ConstU32(3U)), input.param_index),
|
||||
return Name(DefineInput(TypeArray(type, ConstU32(3U)), bind_location),
|
||||
fmt::format("fs_in_attr{}_p", i));
|
||||
}
|
||||
return Name(DefineInput(type, input.param_index), fmt::format("fs_in_attr{}", i));
|
||||
return Name(DefineInput(type, bind_location), fmt::format("fs_in_attr{}", i));
|
||||
}();
|
||||
if (primary == Qualifier::PerVertex) {
|
||||
Decorate(attr_id, profile.supports_amd_shader_explicit_vertex_parameter
|
||||
@ -450,7 +457,15 @@ void EmitContext::DefineInputs() {
|
||||
input_params[i] = GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components,
|
||||
false, false, primary == Qualifier::PerVertex);
|
||||
}
|
||||
|
||||
if (has_clip_distance_inputs) {
|
||||
const auto type = F32[MaxEmulatedClipDistances];
|
||||
const auto attr_id = Name(DefineInput(type, 0), fmt::format("cldist_attr{}", 0));
|
||||
input_params[num_inputs] = GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id,
|
||||
MaxEmulatedClipDistances, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LogicalStage::Compute:
|
||||
if (info.loads.GetAny(IR::Attribute::WorkgroupIndex) ||
|
||||
info.loads.GetAny(IR::Attribute::WorkgroupId)) {
|
||||
@ -546,11 +561,16 @@ void EmitContext::DefineVertexBlock() {
|
||||
const std::array<Id, 8> zero{f32_zero_value, f32_zero_value, f32_zero_value, f32_zero_value,
|
||||
f32_zero_value, f32_zero_value, f32_zero_value, f32_zero_value};
|
||||
output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output);
|
||||
if (info.stores.GetAny(IR::Attribute::ClipDistance)) {
|
||||
const Id type{TypeArray(F32[1], ConstU32(8U))};
|
||||
const Id initializer{ConstantComposite(type, zero)};
|
||||
clip_distances = DefineVariable(type, spv::BuiltIn::ClipDistance, spv::StorageClass::Output,
|
||||
initializer);
|
||||
const bool needs_clip_distance_emulation = l_stage == LogicalStage::Vertex &&
|
||||
stage == Stage::Vertex &&
|
||||
profile.needs_clip_distance_emulation;
|
||||
if (!needs_clip_distance_emulation) {
|
||||
if (info.stores.GetAny(IR::Attribute::ClipDistance)) {
|
||||
const Id type{TypeArray(F32[1], ConstU32(8U))};
|
||||
const Id initializer{ConstantComposite(type, zero)};
|
||||
clip_distances = DefineVariable(type, spv::BuiltIn::ClipDistance,
|
||||
spv::StorageClass::Output, initializer);
|
||||
}
|
||||
}
|
||||
if (info.stores.GetAny(IR::Attribute::CullDistance)) {
|
||||
const Id type{TypeArray(F32[1], ConstU32(8U))};
|
||||
@ -583,16 +603,27 @@ void EmitContext::DefineOutputs() {
|
||||
Name(output_attr_array, "out_attrs");
|
||||
}
|
||||
} else {
|
||||
const auto has_clip_distance_outputs = info.stores.GetAny(IR::Attribute::ClipDistance);
|
||||
u32 num_attrs = 0u;
|
||||
for (u32 i = 0; i < IR::NumParams; i++) {
|
||||
const IR::Attribute param{IR::Attribute::Param0 + i};
|
||||
if (!info.stores.GetAny(param)) {
|
||||
continue;
|
||||
}
|
||||
const u32 num_components = info.stores.NumComponents(param);
|
||||
const Id id{DefineOutput(F32[num_components], i)};
|
||||
const Id id{
|
||||
DefineOutput(F32[num_components], i + (has_clip_distance_outputs ? 1 : 0))};
|
||||
Name(id, fmt::format("out_attr{}", i));
|
||||
output_params[i] =
|
||||
GetAttributeInfo(AmdGpu::NumberFormat::Float, id, num_components, true);
|
||||
++num_attrs;
|
||||
}
|
||||
|
||||
if (has_clip_distance_outputs) {
|
||||
clip_distances = Id{DefineOutput(F32[MaxEmulatedClipDistances], 0)};
|
||||
output_params[num_attrs] = GetAttributeInfo(
|
||||
AmdGpu::NumberFormat::Float, clip_distances, MaxEmulatedClipDistances, true);
|
||||
Name(clip_distances, fmt::format("cldist_attr{}", 0));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -101,7 +101,7 @@ std::string NameOf(Attribute attribute) {
|
||||
case Attribute::Param31:
|
||||
return "Param31";
|
||||
case Attribute::ClipDistance:
|
||||
return "ClipDistanace";
|
||||
return "ClipDistance";
|
||||
case Attribute::CullDistance:
|
||||
return "CullDistance";
|
||||
case Attribute::RenderTargetIndex:
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "shader_recompiler/info.h"
|
||||
#include "shader_recompiler/ir/basic_block.h"
|
||||
#include "shader_recompiler/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/ir/program.h"
|
||||
|
||||
namespace Shader {
|
||||
|
||||
void InjectClipDistanceAttributes(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||
auto& info = runtime_info.fs_info;
|
||||
|
||||
if (!info.clip_distance_emulation || program.info.l_stage != LogicalStage::Fragment) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* first_block = *program.blocks.begin();
|
||||
auto it = std::ranges::find_if(first_block->Instructions(), [](const IR::Inst& inst) {
|
||||
return inst.GetOpcode() == IR::Opcode::Prologue;
|
||||
});
|
||||
ASSERT(it != first_block->end());
|
||||
++it;
|
||||
ASSERT(it != first_block->end());
|
||||
++it;
|
||||
|
||||
IR::IREmitter ir{*first_block, it};
|
||||
|
||||
// We don't know how many clip distances are exported by VS as it is not processed at this point
|
||||
// yet. Here is an assumption that we will have not more than 4 of them (while max is 8) to save
|
||||
// one attributes export slot.
|
||||
const auto attrib = IR::Attribute::Param0 + info.num_inputs;
|
||||
for (u32 comp = 0; comp < MaxEmulatedClipDistances; ++comp) {
|
||||
const auto attr_read = ir.GetAttribute(attrib, comp);
|
||||
const auto cond_id = ir.FPLessThan(attr_read, ir.Imm32(0.0f));
|
||||
ir.Discard(cond_id);
|
||||
}
|
||||
++info.num_inputs;
|
||||
}
|
||||
|
||||
} // namespace Shader
|
||||
@ -8,7 +8,8 @@
|
||||
|
||||
namespace Shader {
|
||||
struct Profile;
|
||||
}
|
||||
void InjectClipDistanceAttributes(IR::Program& program, RuntimeInfo& runtime_info);
|
||||
} // namespace Shader
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
||||
|
||||
@ -660,6 +660,7 @@ void PatchGlobalDataShareAccess(IR::Block& block, IR::Inst& inst, Info& info,
|
||||
inst.SetArg(1, ir.Imm32(binding));
|
||||
} else {
|
||||
// Convert shared memory opcode to storage buffer atomic to GDS buffer.
|
||||
auto& buffer = info.buffers[binding];
|
||||
const IR::U32 offset = IR::U32{inst.Arg(0)};
|
||||
const IR::U32 address_words = ir.ShiftRightLogical(offset, ir.Imm32(1));
|
||||
const IR::U32 address_dwords = ir.ShiftRightLogical(offset, ir.Imm32(2));
|
||||
@ -705,27 +706,35 @@ void PatchGlobalDataShareAccess(IR::Block& block, IR::Inst& inst, Info& info,
|
||||
case IR::Opcode::SharedAtomicXor32:
|
||||
inst.ReplaceUsesWith(ir.BufferAtomicXor(handle, address_dwords, inst.Arg(1), {}));
|
||||
break;
|
||||
case IR::Opcode::LoadSharedU16:
|
||||
case IR::Opcode::LoadSharedU16: {
|
||||
inst.ReplaceUsesWith(ir.LoadBufferU16(handle, address_words, {}));
|
||||
buffer.used_types |= IR::Type::U16;
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::LoadSharedU32:
|
||||
inst.ReplaceUsesWith(ir.LoadBufferU32(1, handle, address_dwords, {}));
|
||||
break;
|
||||
case IR::Opcode::LoadSharedU64:
|
||||
case IR::Opcode::LoadSharedU64: {
|
||||
inst.ReplaceUsesWith(ir.LoadBufferU64(handle, address_qwords, {}));
|
||||
buffer.used_types |= IR::Type::U64;
|
||||
break;
|
||||
case IR::Opcode::WriteSharedU16:
|
||||
}
|
||||
case IR::Opcode::WriteSharedU16: {
|
||||
ir.StoreBufferU16(handle, address_words, IR::U16{inst.Arg(1)}, {});
|
||||
inst.Invalidate();
|
||||
buffer.used_types |= IR::Type::U16;
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::WriteSharedU32:
|
||||
ir.StoreBufferU32(1, handle, address_dwords, inst.Arg(1), {});
|
||||
inst.Invalidate();
|
||||
break;
|
||||
case IR::Opcode::WriteSharedU64:
|
||||
case IR::Opcode::WriteSharedU64: {
|
||||
ir.StoreBufferU64(handle, address_qwords, IR::U64{inst.Arg(1)}, {});
|
||||
inst.Invalidate();
|
||||
buffer.used_types |= IR::Type::U64;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ struct Profile {
|
||||
bool needs_lds_barriers{};
|
||||
bool needs_buffer_offsets{};
|
||||
bool needs_unorm_fixup{};
|
||||
bool _pad0{};
|
||||
bool needs_clip_distance_emulation{};
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
|
||||
@ -13,17 +13,16 @@ namespace Shader {
|
||||
|
||||
IR::BlockList GenerateBlocks(const IR::AbstractSyntaxList& syntax_list) {
|
||||
size_t num_syntax_blocks{};
|
||||
for (const auto& node : syntax_list) {
|
||||
if (node.type == IR::AbstractSyntaxNode::Type::Block) {
|
||||
for (const auto& [_, type] : syntax_list) {
|
||||
if (type == IR::AbstractSyntaxNode::Type::Block) {
|
||||
++num_syntax_blocks;
|
||||
}
|
||||
}
|
||||
IR::BlockList blocks;
|
||||
IR::BlockList blocks{};
|
||||
blocks.reserve(num_syntax_blocks);
|
||||
u32 order_index{};
|
||||
for (const auto& node : syntax_list) {
|
||||
if (node.type == IR::AbstractSyntaxNode::Type::Block) {
|
||||
blocks.push_back(node.data.block);
|
||||
for (const auto& [data, type] : syntax_list) {
|
||||
if (type == IR::AbstractSyntaxNode::Type::Block) {
|
||||
blocks.push_back(data.block);
|
||||
}
|
||||
}
|
||||
return blocks;
|
||||
@ -60,6 +59,10 @@ IR::Program TranslateProgram(const std::span<const u32>& code, Pools& pools, Inf
|
||||
program.blocks = GenerateBlocks(program.syntax_list);
|
||||
program.post_order_blocks = Shader::IR::PostOrder(program.syntax_list.front());
|
||||
|
||||
// On NVIDIA GPUs HW interpolation of clip distance values seems broken, and we need to emulate
|
||||
// it with expensive discard in PS.
|
||||
Shader::InjectClipDistanceAttributes(program, runtime_info);
|
||||
|
||||
// Run optimization passes
|
||||
if (!profile.support_float64) {
|
||||
Shader::Optimization::LowerFp64ToFp32(program);
|
||||
|
||||
@ -34,6 +34,7 @@ enum class LogicalStage : u32 {
|
||||
};
|
||||
|
||||
constexpr u32 MaxStageTypes = static_cast<u32>(LogicalStage::NumLogicalStages);
|
||||
constexpr auto MaxEmulatedClipDistances = 4u;
|
||||
|
||||
constexpr Stage StageFromIndex(size_t index) noexcept {
|
||||
return static_cast<Stage>(index);
|
||||
@ -201,14 +202,16 @@ struct FragmentRuntimeInfo {
|
||||
std::array<PsInput, 32> inputs;
|
||||
std::array<PsColorBuffer, MaxColorBuffers> color_buffers;
|
||||
AmdGpu::ShaderExportFormat z_export_format;
|
||||
u8 mrtz_mask;
|
||||
bool dual_source_blending;
|
||||
u8 mrtz_mask{};
|
||||
bool dual_source_blending{false};
|
||||
bool clip_distance_emulation{false};
|
||||
|
||||
bool operator==(const FragmentRuntimeInfo& other) const noexcept {
|
||||
return std::ranges::equal(color_buffers, other.color_buffers) &&
|
||||
en_flags == other.en_flags && addr_flags == other.addr_flags &&
|
||||
num_inputs == other.num_inputs && z_export_format == other.z_export_format &&
|
||||
mrtz_mask == other.mrtz_mask && dual_source_blending == other.dual_source_blending &&
|
||||
clip_distance_emulation == other.clip_distance_emulation &&
|
||||
std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(),
|
||||
other.inputs.begin() + num_inputs);
|
||||
}
|
||||
|
||||
@ -831,7 +831,14 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
|
||||
FIBER_ENTER(acb_task_name[vqid]);
|
||||
auto& queue = asc_queues[{vqid}];
|
||||
|
||||
struct IndirectPatch {
|
||||
const PM4Header* header;
|
||||
VAddr indirect_addr;
|
||||
};
|
||||
boost::container::small_vector<IndirectPatch, 4> indirect_patches;
|
||||
|
||||
auto base_addr = reinterpret_cast<VAddr>(acb.data());
|
||||
size_t acb_size = acb.size_bytes();
|
||||
while (!acb.empty()) {
|
||||
ProcessCommands();
|
||||
|
||||
@ -920,8 +927,18 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
|
||||
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
|
||||
(dma_data->dst_sel == DmaDataDst::Memory ||
|
||||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
|
||||
rasterizer->CopyBuffer(dma_data->DstAddress<VAddr>(), dma_data->SrcAddress<VAddr>(),
|
||||
dma_data->NumBytes(), false, false);
|
||||
const u32 num_bytes = dma_data->NumBytes();
|
||||
const VAddr src_addr = dma_data->SrcAddress<VAddr>();
|
||||
const VAddr dst_addr = dma_data->DstAddress<VAddr>();
|
||||
const PM4Header* header =
|
||||
reinterpret_cast<const PM4Header*>(dst_addr - sizeof(PM4Header));
|
||||
if (dst_addr >= base_addr && dst_addr < base_addr + acb_size &&
|
||||
num_bytes == sizeof(PM4CmdDispatchIndirect::GroupDimensions) &&
|
||||
header->type == 3 && header->type3.opcode == PM4ItOpcode::DispatchDirect) {
|
||||
indirect_patches.emplace_back(header, src_addr);
|
||||
} else {
|
||||
rasterizer->CopyBuffer(dst_addr, src_addr, num_bytes, false, false);
|
||||
}
|
||||
} else {
|
||||
UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}",
|
||||
u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value()));
|
||||
@ -965,6 +982,12 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
|
||||
}
|
||||
case PM4ItOpcode::DispatchDirect: {
|
||||
const auto* dispatch_direct = reinterpret_cast<const PM4CmdDispatchDirect*>(header);
|
||||
if (auto it = std::ranges::find(indirect_patches, header, &IndirectPatch::header);
|
||||
it != indirect_patches.end()) {
|
||||
const auto size = sizeof(PM4CmdDispatchIndirect::GroupDimensions);
|
||||
rasterizer->DispatchIndirect(it->indirect_addr, 0, size);
|
||||
break;
|
||||
}
|
||||
auto& cs_program = GetCsRegs();
|
||||
cs_program.dim_x = dispatch_direct->dim_x;
|
||||
cs_program.dim_y = dispatch_direct->dim_y;
|
||||
@ -1035,9 +1058,13 @@ Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
|
||||
}
|
||||
case PM4ItOpcode::ReleaseMem: {
|
||||
const auto* release_mem = reinterpret_cast<const PM4CmdReleaseMem*>(header);
|
||||
release_mem->SignalFence([pipe_id = queue.pipe_id] {
|
||||
Platform::IrqC::Instance()->Signal(static_cast<Platform::InterruptId>(pipe_id));
|
||||
});
|
||||
release_mem->SignalFence(
|
||||
[pipe_id = queue.pipe_id] {
|
||||
Platform::IrqC::Instance()->Signal(static_cast<Platform::InterruptId>(pipe_id));
|
||||
},
|
||||
[this](VAddr dst, u16 gds_index, u16 num_dwords) {
|
||||
rasterizer->CopyBuffer(dst, gds_index, num_dwords * sizeof(u32), false, true);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case PM4ItOpcode::EventWrite: {
|
||||
|
||||
@ -50,7 +50,7 @@ union PM4Type3Header {
|
||||
}
|
||||
|
||||
u32 NumWords() const {
|
||||
return count + 1;
|
||||
return (count + 1) & 0x3fff;
|
||||
}
|
||||
|
||||
u32 raw;
|
||||
@ -327,6 +327,7 @@ enum class DataSelect : u32 {
|
||||
Data64 = 2,
|
||||
GpuClock64 = 3,
|
||||
PerfCounter = 4,
|
||||
GdsMemStore = 5,
|
||||
};
|
||||
|
||||
enum class InterruptSelect : u32 {
|
||||
@ -920,8 +921,9 @@ struct PM4CmdReleaseMem {
|
||||
u32 data_hi;
|
||||
|
||||
template <typename T>
|
||||
T* Address() const {
|
||||
return reinterpret_cast<T*>(address_lo | u64(address_hi) << 32);
|
||||
T Address() const {
|
||||
u64 full_address = address_lo | (u64(address_hi) << 32);
|
||||
return std::bit_cast<T>(full_address);
|
||||
}
|
||||
|
||||
u32 DataDWord() const {
|
||||
@ -932,22 +934,26 @@ struct PM4CmdReleaseMem {
|
||||
return data_lo | u64(data_hi) << 32;
|
||||
}
|
||||
|
||||
void SignalFence(auto&& signal_irq) const {
|
||||
void SignalFence(auto&& signal_irq, auto&& gds_to_mem) const {
|
||||
switch (data_sel.Value()) {
|
||||
case DataSelect::Data32Low: {
|
||||
*Address<u32>() = DataDWord();
|
||||
*Address<u32*>() = DataDWord();
|
||||
break;
|
||||
}
|
||||
case DataSelect::Data64: {
|
||||
*Address<u64>() = DataQWord();
|
||||
*Address<u64*>() = DataQWord();
|
||||
break;
|
||||
}
|
||||
case DataSelect::GpuClock64: {
|
||||
*Address<u64>() = GetGpuClock64();
|
||||
*Address<u64*>() = GetGpuClock64();
|
||||
break;
|
||||
}
|
||||
case DataSelect::PerfCounter: {
|
||||
*Address<u64>() = GetGpuPerfCounter();
|
||||
*Address<u64*>() = GetGpuPerfCounter();
|
||||
break;
|
||||
}
|
||||
case DataSelect::GdsMemStore: {
|
||||
gds_to_mem(Address<VAddr>(), gds_index, num_dw);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
||||
@ -102,7 +102,7 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS
|
||||
switch (stage) {
|
||||
case Stage::Local: {
|
||||
BuildCommon(regs.ls_program);
|
||||
Shader::TessellationDataConstantBuffer tess_constants;
|
||||
Shader::TessellationDataConstantBuffer tess_constants{};
|
||||
const auto* hull_info = infos[u32(Shader::LogicalStage::TessellationControl)];
|
||||
hull_info->ReadTessConstantBuffer(tess_constants);
|
||||
info.ls_info.ls_stride = tess_constants.ls_stride;
|
||||
@ -200,6 +200,10 @@ const Shader::RuntimeInfo& PipelineCache::BuildRuntimeInfo(Stage stage, LogicalS
|
||||
for (u32 i = 0; i < Shader::MaxColorBuffers; i++) {
|
||||
info.fs_info.color_buffers[i] = graphics_key.color_buffers[i];
|
||||
}
|
||||
info.fs_info.clip_distance_emulation =
|
||||
regs.vs_output_control.clip_distance_enable &&
|
||||
!regs.stage_enable.IsStageEnabled(static_cast<u32>(Stage::Local)) &&
|
||||
profile.needs_clip_distance_emulation;
|
||||
break;
|
||||
}
|
||||
case Stage::Compute: {
|
||||
@ -267,6 +271,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
instance.GetDriverID() == vk::DriverId::eMoltenvk,
|
||||
.needs_buffer_offsets = instance.StorageMinAlignment() > 4,
|
||||
.needs_unorm_fixup = instance.GetDriverID() == vk::DriverId::eMoltenvk,
|
||||
.needs_clip_distance_emulation = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary,
|
||||
};
|
||||
|
||||
WarmUp();
|
||||
@ -461,7 +466,13 @@ bool PipelineCache::RefreshGraphicsStages() {
|
||||
|
||||
infos.fill(nullptr);
|
||||
modules.fill(nullptr);
|
||||
bind_stage(Stage::Fragment, LogicalStage::Fragment);
|
||||
const auto result = bind_stage(Stage::Fragment, LogicalStage::Fragment);
|
||||
if (!result && regs.vs_output_control.clip_distance_enable &&
|
||||
profile.needs_clip_distance_emulation) {
|
||||
// TODO: need to implement a discard only fallback shader
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Clip distance emulation is ineffective due to absense of fragment shader");
|
||||
}
|
||||
|
||||
const auto* fs_info = infos[static_cast<u32>(LogicalStage::Fragment)];
|
||||
key.mrt_mask = fs_info ? fs_info->mrt_mask : 0u;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user