Merge branch 'shadps4-emu:main' into IRQv5

This commit is contained in:
Kravickas 2026-03-31 15:28:38 +02:00 committed by GitHub
commit ed87ea4d0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 602 additions and 102 deletions

3
.gitignore vendored
View File

@ -418,3 +418,6 @@ FodyWeavers.xsd
# JetBrains
.idea
cmake-build-*
# Nix Result symlink
result

View File

@ -245,7 +245,7 @@ find_package(VulkanMemoryAllocator 3.1.0 CONFIG)
find_package(xbyak 7.07 CONFIG)
find_package(xxHash 0.8.2 MODULE)
find_package(ZLIB 1.3 MODULE)
find_package(Zydis 5.0.0 CONFIG)
find_package(Zydis 5.0.0 MODULE)
find_package(pugixml 1.14 CONFIG)
if (APPLE)
find_package(date 3.0.1 CONFIG)
@ -1091,6 +1091,8 @@ set(IMGUI src/imgui/imgui_config.h
src/imgui/imgui_layer.h
src/imgui/imgui_std.h
src/imgui/imgui_texture.h
src/imgui/imgui_translations.cpp
src/imgui/imgui_translations.h
src/imgui/renderer/imgui_core.cpp
src/imgui/renderer/imgui_core.h
src/imgui/renderer/imgui_impl_sdl3.cpp
@ -1137,7 +1139,14 @@ 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::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib)
target_link_libraries(shadps4 PRIVATE stb::headers lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json miniz::miniz fdk-aac CLI11::CLI11 OpenAL::OpenAL Cpp_Httplib)
if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
target_link_libraries(shadps4 PRIVATE "/usr/lib/libusb.so")
target_link_libraries(shadps4 PRIVATE "/usr/local/lib/libuuid.so")
else()
target_link_libraries(shadps4 PRIVATE libusb::usb)
endif()
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")
@ -1183,6 +1192,8 @@ if (APPLE)
# Replacement for std::chrono::time_zone
target_link_libraries(shadps4 PRIVATE date::date-tz epoll-shim)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
target_link_libraries(shadps4 PRIVATE date::date-tz epoll-shim)
endif()
if (WIN32)
@ -1274,4 +1285,4 @@ install(TARGETS shadps4 BUNDLE DESTINATION .)
else()
enable_testing()
add_subdirectory(tests)
endif()
endif()

View File

@ -22,6 +22,7 @@ path = [
"documents/Screenshots/Linux/*",
"documents/Screenshots/Windows/*",
"externals/MoltenVK/MoltenVK_icd.json",
"flake.lock",
"scripts/ps4_names.txt",
"src/images/bronze.png",
"src/images/gold.png",
@ -130,4 +131,4 @@ SPDX-License-Identifier = "MIT"
[[annotations]]
path = "src/video_core/host_shaders/fsr/*"
SPDX-FileCopyrightText = "Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved."
SPDX-License-Identifier = "MIT"
SPDX-License-Identifier = "MIT"

View File

@ -53,6 +53,23 @@ sudo zypper install clang git cmake libasound2 libpulse-devel \
nix-shell shell.nix
```
#### Nix Flake Development Shell
```bash
nix develop
cmake -S . -B build/ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
ln -s ./build/compile_commands.json .
```
#### Nix Flake Build
```bash
nix build .?submodules=1#linux.debug
```
```bash
nix build .?submodules=1#linux.release
```
```bash
nix build .?submodules=1#linux.releaseWithDebugInfo
```
#### Other Linux distributions
You can try one of two methods:

View File

@ -210,8 +210,15 @@ endif()
# libusb
if (NOT TARGET libusb::usb)
add_subdirectory(ext-libusb)
add_library(libusb::usb ALIAS usb-1.0)
if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
# YOU MUST USE NATIVE LIBUSB
# using anything else will crash instantly, also freebsd will NOT like it
# no you cant vendor this libusb, its builtin on freebsd
find_package(libusb)
else()
add_subdirectory(ext-libusb)
add_library(libusb::usb ALIAS usb-1.0)
endif()
endif()
# Discord RPC
@ -233,25 +240,26 @@ endif()
set(HWINFO_STATIC ON)
add_subdirectory(hwinfo)
# Apple-only dependencies
if (APPLE)
if (APPLE OR ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
# date
if (NOT TARGET date::date-tz)
option(BUILD_TZ_LIB "" ON)
option(USE_SYSTEM_TZ_DB "" ON)
add_subdirectory(date)
endif()
if (NOT TARGET epoll-shim)
add_subdirectory(epoll-shim)
endif()
endif()
# Apple-only dependencies
if (APPLE)
# MoltenVK
if (NOT TARGET MoltenVK)
set(MVK_EXCLUDE_SPIRV_TOOLS ON)
set(MVK_USE_METAL_PRIVATE_API ON)
add_subdirectory(MoltenVK)
endif()
if (NOT TARGET epoll-shim)
add_subdirectory(epoll-shim)
endif()
endif()
#windows only

27
flake.lock generated Normal file
View File

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1774386573,
"narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

160
flake.nix Normal file
View File

@ -0,0 +1,160 @@
## SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
## SPDX-License-Identifier: GPL-2.0-or-later
{
description = "shadPS4 Nix Flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs =
{ self, nixpkgs }:
let
pkgsLinux = nixpkgs.legacyPackages.x86_64-linux;
in
{
devShells.x86_64-linux.default = pkgsLinux.mkShell.override { stdenv = pkgsLinux.clangStdenv; } {
packages = with pkgsLinux; [
clang-tools
cmake
pkg-config
vulkan-tools
renderdoc
gef
strace
openal
zlib.dev
libedit.dev
vulkan-headers
vulkan-utility-libraries
ffmpeg.dev
fmt.dev
glslang.dev
wayland.dev
stb
libpng.dev
libuuid
sdl3.dev
alsa-lib
hidapi
ibus.dev
jack2.dev
libdecor.dev
libthai.dev
fribidi.dev
libxcb.dev
libGL.dev
libpulseaudio.dev
libusb1.dev
libx11.dev
libxcursor.dev
libxext
libxfixes.dev
libxi.dev
libxinerama.dev
libxkbcommon
libxrandr.dev
libxrender.dev
libxtst
pipewire.dev
libxscrnsaver
sndio
];
shellHook = ''
echo "Entering shadPS4 development shell!"
'';
};
linux =
let
execName = "shadps4";
nativeInputs = with pkgsLinux; [
cmake
ninja
pkg-config
magic-enum
fmt
eudev
];
buildInputs = with pkgsLinux; [
boost
cli11
openal
nlohmann_json
vulkan-loader
vulkan-headers
vulkan-memory-allocator
toml11
zlib
zydis
pugixml
ffmpeg
libpulseaudio
pipewire
vulkan-loader
wayland
wayland-scanner
libX11
libxrandr
libxext
libxcursor
libxi
libxscrnsaver
libxtst
libxcb
libdecor
libxkbcommon
libGL
libuuid
];
defaultFlags = [
"-DCMAKE_INSTALL_PREFIX=$out"
];
in
{
debug = pkgsLinux.stdenv.mkDerivation {
pname = "${execName}";
version = "git";
system = "x86_64-linux";
src = ./.;
dontStrip = true;
nativeBuildInputs = nativeInputs;
buildInputs = buildInputs;
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Debug"
] ++ [defaultFlags];
};
release = pkgsLinux.stdenv.mkDerivation {
pname = "${execName}";
version = "git";
system = "x86_64-linux";
src = ./.;
nativeBuildInputs = nativeInputs;
buildInputs = buildInputs;
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
] ++ [defaultFlags];
};
releaseWithDebugInfo = pkgsLinux.stdenv.mkDerivation {
pname = "${execName}";
version = "git";
system = "x86_64-linux";
src = ./.;
dontStrip = true;
nativeBuildInputs = nativeInputs;
buildInputs = buildInputs;
cmakeFlags = [
"-DCMAKE_BUILD_TYPE=Release"
] ++ [defaultFlags];
};
};
};
}

View File

@ -3,7 +3,7 @@
#pragma once
#ifdef __linux__
#if __unix__
#include <pthread.h>
#endif

View File

@ -7,6 +7,9 @@
#ifdef _WIN32
#include <windows.h>
#elif defined(__FreeBSD__)
#include <machine/npx.h>
#include <sys/ucontext.h>
#else
#include <sys/ucontext.h>
#endif
@ -22,6 +25,16 @@ void* GetXmmPointer(void* ctx, u8 index) {
#define CASE(index) \
case index: \
return (void*)(&((ucontext_t*)ctx)->uc_mcontext->__fs.__fpu_xmm##index);
#elif defined(__FreeBSD__)
// In mc_fpstate
// See <machine/npx.h> for the internals of mc_fpstate[].
#define CASE(index) \
case index: { \
auto& mctx = ((ucontext_t*)ctx)->uc_mcontext; \
ASSERT(mctx.mc_fpformat == _MC_FPFMT_XMM); \
auto* s_fpu = (struct savefpu*)(&mctx.mc_fpstate[0]); \
return (void*)(&(s_fpu->sv_xmm[0])); \
}
#else
#define CASE(index) \
case index: \
@ -57,6 +70,8 @@ void* GetRip(void* ctx) {
return (void*)((EXCEPTION_POINTERS*)ctx)->ContextRecord->Rip;
#elif defined(__APPLE__)
return (void*)((ucontext_t*)ctx)->uc_mcontext->__ss.__rip;
#elif defined(__FreeBSD__)
return (void*)((ucontext_t*)ctx)->uc_mcontext.mc_rip;
#else
return (void*)((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP];
#endif
@ -67,6 +82,8 @@ void IncrementRip(void* ctx, u64 length) {
((EXCEPTION_POINTERS*)ctx)->ContextRecord->Rip += length;
#elif defined(__APPLE__)
((ucontext_t*)ctx)->uc_mcontext->__ss.__rip += length;
#elif defined(__FreeBSD__)
((ucontext_t*)ctx)->uc_mcontext.mc_rip += length;
#else
((ucontext_t*)ctx)->uc_mcontext.gregs[REG_RIP] += length;
#endif
@ -75,18 +92,16 @@ void IncrementRip(void* ctx, u64 length) {
bool IsWriteError(void* ctx) {
#if defined(_WIN32)
return ((EXCEPTION_POINTERS*)ctx)->ExceptionRecord->ExceptionInformation[0] == 1;
#elif defined(__APPLE__)
#if defined(ARCH_X86_64)
#elif defined(__APPLE__) && defined(ARCH_X86_64)
return ((ucontext_t*)ctx)->uc_mcontext->__es.__err & 0x2;
#elif defined(ARCH_ARM64)
#elif defined(__APPLE__) && defined(ARCH_ARM64)
return ((ucontext_t*)ctx)->uc_mcontext->__es.__esr & 0x40;
#endif
#else
#if defined(ARCH_X86_64)
#elif defined(__FreeBSD__) && defined(ARCH_X86_64)
return ((ucontext_t*)ctx)->uc_mcontext.mc_err & 0x2;
#elif defined(ARCH_X86_64)
return ((ucontext_t*)ctx)->uc_mcontext.gregs[REG_ERR] & 0x2;
#else
#error "Unsupported architecture"
#endif
#endif
}
} // namespace Common
} // namespace Common

View File

@ -613,7 +613,11 @@ struct AddressSpace::Impl {
user_size = UserSize;
constexpr int protection_flags = PROT_READ | PROT_WRITE;
constexpr int map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED;
int map_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED; // compiler knows its constexpr
#if !defined(__FreeBSD__)
map_flags |= MAP_NORESERVE;
#endif
#if defined(__APPLE__) && defined(ARCH_X86_64)
// On ARM64 Macs, we run into limitations due to the commpage from 0xFC0000000 - 0xFFFFFFFFF
// and the GPU carveout region from 0x1000000000 - 0x6FFFFFFFFF. Because this creates gaps
@ -628,7 +632,7 @@ struct AddressSpace::Impl {
mmap(reinterpret_cast<void*>(USER_MIN), user_size, protection_flags, map_flags, -1, 0));
#else
const auto virtual_size = system_managed_size + system_reserved_size + user_size;
#if defined(ARCH_X86_64)
#if defined(ARCH_X86_64) && !defined(__FreeBSD__)
const auto virtual_base =
reinterpret_cast<u8*>(mmap(reinterpret_cast<void*>(SYSTEM_MANAGED_MIN), virtual_size,
protection_flags, map_flags, -1, 0));
@ -636,8 +640,10 @@ struct AddressSpace::Impl {
system_reserved_base = reinterpret_cast<u8*>(SYSTEM_RESERVED_MIN);
user_base = reinterpret_cast<u8*>(USER_MIN);
#else
// FreeBSD can't stand MAP_FIXED or it may overwrite mmap() itself!
// Map memory wherever possible and instruction translation can handle offsetting to the
// base.
map_flags &= ~MAP_FIXED;
const auto virtual_base =
reinterpret_cast<u8*>(mmap(nullptr, virtual_size, protection_flags, map_flags, -1, 0));
system_managed_base = virtual_base;
@ -676,8 +682,13 @@ struct AddressSpace::Impl {
}
shm_unlink(shm_path.c_str());
#else
#ifndef __FreeBSD__
madvise(virtual_base, virtual_size, MADV_HUGEPAGE);
#endif
// NOTE: If you add MFD_HUGETLB or whatever, remember that FBSD will break (libc bug)
// so please, do not, add MFD_* whatever unless you ifdef it away (must be 0 for FBSD)
// using sized pages as well causes incessant vm_reclaim calls in kernel, do not use on FBSD
// under any circumstances.
backing_fd = memfd_create("BackingDmem", 0);
if (backing_fd < 0) {
LOG_CRITICAL(Kernel_Vmm, "memfd_create failed: {}", strerror(errno));

View File

@ -12,7 +12,7 @@
#elif defined(__linux__)
#include <filesystem>
#include <fstream>
#elif defined(__APPLE__)
#elif defined(__APPLE__) || defined(__FreeBSD__)
#include <errno.h>
#include <signal.h>
#include <sys/sysctl.h>
@ -48,6 +48,8 @@ bool Core::Debugger::IsDebuggerAttached() {
return (info.kp_proc.p_flag & P_TRACED) != 0;
}
return false;
#elif defined(__FreeBSD__)
return false;
#else
#error "Unsupported platform"
#endif
@ -66,7 +68,7 @@ void Core::Debugger::WaitForDebuggerAttach() {
int Core::Debugger::GetCurrentPid() {
#if defined(_WIN32)
return GetCurrentProcessId();
#elif defined(__APPLE__) || defined(__linux__)
#elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__)
return getpid();
#else
#error "Unsupported platform"
@ -88,7 +90,7 @@ void Core::Debugger::WaitForPid(int pid) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cerr << "Waiting for process " << pid << " to exit..." << std::endl;
}
#elif defined(__APPLE__)
#elif defined(__APPLE__) || defined(__FreeBSD__)
while (kill(pid, 0) == 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cerr << "Waiting for process " << pid << " to exit..." << std::endl;

View File

@ -95,7 +95,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
std::scoped_lock lk{m_mutex};
path_parts.clear();
auto current_path = host_path;
while (!std::filesystem::exists(current_path)) {
while (!current_path.empty() && !std::filesystem::exists(current_path)) {
// We have probably cached this if it's a folder.
if (auto it = path_cache.find(current_path); it != path_cache.end()) {
current_path = it->second;
@ -104,38 +104,40 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
path_parts.emplace_back(current_path.filename());
current_path = current_path.parent_path();
}
// We have found an anchor. Traverse parts we recoded and see if they
// exist in filesystem but in different case.
auto guest_path = current_path;
while (!path_parts.empty()) {
const auto part = path_parts.back();
const auto add_match = [&](const auto& host_part) {
current_path /= host_part;
guest_path /= part;
path_cache[guest_path] = current_path;
path_parts.pop_back();
};
// Can happen when the mismatch is in upper folder.
if (std::filesystem::exists(current_path / part)) {
add_match(part);
continue;
}
const auto part_low = Common::ToLower(part.string());
bool found_match = false;
for (const auto& path : std::filesystem::directory_iterator(current_path)) {
const auto candidate = path.path().filename();
const auto filename = Common::ToLower(candidate.string());
// Check if a filename matches in case insensitive manner.
if (filename != part_low) {
if (!current_path.empty()) {
// We have found an anchor. Traverse parts we recoded and see if they
// exist in filesystem but in different case.
auto guest_path = current_path;
while (!path_parts.empty()) {
const auto part = path_parts.back();
const auto add_match = [&](const auto& host_part) {
current_path /= host_part;
guest_path /= part;
path_cache[guest_path] = current_path;
path_parts.pop_back();
};
// Can happen when the mismatch is in upper folder.
if (std::filesystem::exists(current_path / part)) {
add_match(part);
continue;
}
// We found a match, record the actual path in the cache.
add_match(candidate);
found_match = true;
break;
}
if (!found_match) {
return std::optional<std::filesystem::path>({});
const auto part_low = Common::ToLower(part.string());
bool found_match = false;
for (const auto& path : std::filesystem::directory_iterator(current_path)) {
const auto candidate = path.path().filename();
const auto filename = Common::ToLower(candidate.string());
// Check if a filename matches in case insensitive manner.
if (filename != part_low) {
continue;
}
// We found a match, record the actual path in the cache.
add_match(candidate);
found_match = true;
break;
}
if (!found_match) {
return std::optional<std::filesystem::path>({});
}
}
}
return std::optional<std::filesystem::path>(current_path);

View File

@ -215,6 +215,28 @@ void SigactionHandler(int native_signum, siginfo_t* inf, ucontext_t* raw_context
ctx.uc_mcontext.mc_gs = regs.__gs;
ctx.uc_mcontext.mc_rip = regs.__rip;
ctx.uc_mcontext.mc_addr = reinterpret_cast<uint64_t>(inf->si_addr);
#elif defined(__FreeBSD__)
const auto& regs = raw_context->uc_mcontext;
ctx.uc_mcontext.mc_r8 = regs.mc_r8;
ctx.uc_mcontext.mc_r9 = regs.mc_r9;
ctx.uc_mcontext.mc_r10 = regs.mc_r10;
ctx.uc_mcontext.mc_r11 = regs.mc_r11;
ctx.uc_mcontext.mc_r12 = regs.mc_r12;
ctx.uc_mcontext.mc_r13 = regs.mc_r13;
ctx.uc_mcontext.mc_r14 = regs.mc_r14;
ctx.uc_mcontext.mc_r15 = regs.mc_r15;
ctx.uc_mcontext.mc_rdi = regs.mc_rdi;
ctx.uc_mcontext.mc_rsi = regs.mc_rsi;
ctx.uc_mcontext.mc_rbp = regs.mc_rbp;
ctx.uc_mcontext.mc_rbx = regs.mc_rbx;
ctx.uc_mcontext.mc_rdx = regs.mc_rdx;
ctx.uc_mcontext.mc_rax = regs.mc_rax;
ctx.uc_mcontext.mc_rcx = regs.mc_rcx;
ctx.uc_mcontext.mc_rsp = regs.mc_rsp;
ctx.uc_mcontext.mc_fs = regs.mc_fs;
ctx.uc_mcontext.mc_gs = regs.mc_gs;
ctx.uc_mcontext.mc_rip = regs.mc_rip;
ctx.uc_mcontext.mc_addr = uint64_t(regs.mc_addr);
#else
const auto& regs = raw_context->uc_mcontext.gregs;
ctx.uc_mcontext.mc_r8 = regs[REG_R8];
@ -303,7 +325,7 @@ s32 PS4_SYSV_ABI posix_sigaction(s32 sig, Sigaction* act, Sigaction* oact) {
*__Error() = POSIX_EINVAL;
return ORBIS_FAIL;
}
#ifndef __APPLE__
#if !defined(__APPLE__) && !defined(__FreeBSD__)
if (native_sig >= __SIGRTMIN && native_sig < SIGRTMIN) {
LOG_ERROR(Lib_Kernel, "Guest is attempting to use the HLE libc-reserved signal {}!", sig);
*__Error() = POSIX_EINVAL;

View File

@ -47,7 +47,7 @@ constexpr s32 POSIX_SIGUSR2 = 31;
constexpr s32 POSIX_SIGTHR = 32;
constexpr s32 POSIX_SIGLIBRT = 33;
#ifdef __linux__
#if defined(__linux__) || defined(__FreeBSD__)
constexpr s32 _SIGEMT = 128;
constexpr s32 _SIGINFO = 129;
#elif !defined(_WIN32)

View File

@ -17,7 +17,7 @@
#include <windows.h>
#include "common/ntapi.h"
#else
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__FreeBSD__)
#include <date/tz.h>
#endif
#include <ctime>
@ -501,7 +501,7 @@ s32 PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time,
*dst_sec = res == TIME_ZONE_ID_DAYLIGHT ? -_dstbias : 0;
}
#else
#ifdef __APPLE__
#if defined(__APPLE__) || defined(__FreeBSD__)
// std::chrono::current_zone() not available yet.
const auto* time_zone = date::current_zone();
#else

View File

@ -663,10 +663,12 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
return ORBIS_NET_ERROR_EBADF;
}
#ifndef __FreeBSD__
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
.data = {.fd = id}};
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_ADD, *native_handle, &native_event) == 0);
epoll->events.emplace_back(id, *event);
#endif
break;
}
case Core::FileSys::FileType::Resolver: {
@ -711,10 +713,12 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
return ORBIS_NET_ERROR_EBADF;
}
#ifndef __FreeBSD__
epoll_event native_event = {.events = ConvertEpollEventsIn(event->events),
.data = {.fd = id}};
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_MOD, *native_handle, &native_event) == 0);
*it = {id, *event};
#endif
break;
}
default:
@ -752,9 +756,10 @@ int PS4_SYSV_ABI sceNetEpollControl(OrbisNetId epollid, OrbisNetEpollFlag op, Or
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
return ORBIS_NET_ERROR_EBADF;
}
#ifndef __FreeBSD__
ASSERT(epoll_ctl(epoll->epoll_fd, EPOLL_CTL_DEL, *native_handle, nullptr) == 0);
epoll->events.erase(it);
#endif
break;
}
case Core::FileSys::FileType::Resolver: {
@ -810,6 +815,9 @@ int PS4_SYSV_ABI sceNetEpollDestroy(OrbisNetId epollid) {
int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events, int maxevents,
int timeout) {
#ifdef __FreeBSD__
return 0;
#else
auto file = FDTable::Instance()->GetEpoll(epollid);
if (!file) {
*sceNetErrnoLoc() = ORBIS_NET_EBADF;
@ -836,7 +844,6 @@ int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events,
}
int i = 0;
if (result < 0) {
LOG_ERROR(Lib_Net, "epoll_wait failed with {}", Common::GetLastErrorMsg());
switch (errno) {
@ -905,8 +912,8 @@ int PS4_SYSV_ABI sceNetEpollWait(OrbisNetId epollid, OrbisNetEpollEvent* events,
++i;
}
}
return i;
#endif
}
int* PS4_SYSV_ABI sceNetErrnoLoc() {

View File

@ -10,12 +10,14 @@ namespace Libraries::Net {
u32 ConvertEpollEventsIn(u32 orbis_events) {
u32 ret = 0;
#ifndef __FreeBSD__
if ((orbis_events & ORBIS_NET_EPOLLIN) != 0) {
ret |= EPOLLIN;
}
if ((orbis_events & ORBIS_NET_EPOLLOUT) != 0) {
ret |= EPOLLOUT;
}
#endif
return ret;
}
@ -23,6 +25,7 @@ u32 ConvertEpollEventsIn(u32 orbis_events) {
u32 ConvertEpollEventsOut(u32 epoll_events) {
u32 ret = 0;
#ifndef __FreeBSD__
if ((epoll_events & EPOLLIN) != 0) {
ret |= ORBIS_NET_EPOLLIN;
}
@ -35,6 +38,7 @@ u32 ConvertEpollEventsOut(u32 epoll_events) {
if ((epoll_events & EPOLLHUP) != 0) {
ret |= ORBIS_NET_EPOLLHUP;
}
#endif
return ret;
}

View File

@ -14,7 +14,8 @@
#include <wepoll.h>
#endif
#if defined(__linux__) || defined(__APPLE__)
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
// ADD libepoll-shim if using freebsd!
#include <sys/epoll.h>
#include <unistd.h>
#endif
@ -82,4 +83,4 @@ private:
std::mutex m_mutex;
};
} // namespace Libraries::Net
} // namespace Libraries::Net

View File

@ -25,7 +25,7 @@ typedef int net_socket;
#include <net/if_dl.h>
#include <net/route.h>
#endif
#if __linux__
#if defined(__linux__) || defined(__FreeBSD__)
#include <fstream>
#include <iostream>
#include <sstream>
@ -81,6 +81,8 @@ bool NetUtilInternal::RetrieveEthernetAddr() {
}
freeifaddrs(ifap);
}
#elif defined(__FreeBSD__)
// todo
#else
ifreq ifr;
ifconf ifc;
@ -226,7 +228,8 @@ bool NetUtilInternal::RetrieveDefaultGateway() {
inet_ntop(AF_INET, gateAddr, str, sizeof(str));
this->default_gateway = str;
return true;
#elif defined(__FreeBSD__)
return true;
#else
std::ifstream route{"/proc/net/route"};
std::string line;
@ -398,4 +401,4 @@ int NetUtilInternal::ResolveHostname(const char* hostname, Libraries::Net::Orbis
return ret;
}
} // namespace NetUtil
} // namespace NetUtil

View File

@ -222,8 +222,8 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
const std::string np_comm_id = Common::ElfInfo::Instance().GetNpCommIds()[service_label];
const auto trophy_base =
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "trophy" / np_comm_id;
ctx.xml_save_file = Common::FS::GetUserPath(Common::FS::PathType::HomeDir) /
std::to_string(user_id) / "trophy" / (np_comm_id + ".xml");
ctx.xml_save_file =
EmulatorSettings.GetHomeDir() / std::to_string(user_id) / "trophy" / (np_comm_id + ".xml");
ctx.xml_dir = trophy_base / "Xml";
ctx.icons_dir = trophy_base / "Icons";
ctx.trophy_xml_path = GetTrophyXmlPath(ctx.xml_dir, EmulatorSettings.GetConsoleLanguage());

View File

@ -10,6 +10,8 @@
#ifdef _WIN32
#include <windows.h>
#elif defined(__FreeBSD__)
#include <machine/sysarch.h>
#elif defined(__APPLE__) && defined(ARCH_X86_64)
#include <architecture/i386/table.h>
#include <boost/icl/interval_set.hpp>
@ -157,12 +159,17 @@ Tcb* GetTcbBase() {
#elif defined(ARCH_X86_64)
// Other POSIX x86_64
// Linux x86_64
#if defined(__FreeBSD__)
void SetTcbBase(void* image_address) {
amd64_set_gsbase(image_address);
}
#else
void SetTcbBase(void* image_address) {
const int ret = syscall(SYS_arch_prctl, ARCH_SET_GS, (unsigned long)image_address);
ASSERT_MSG(ret == 0, "Failed to set GS base: errno {}", errno);
}
#endif
Tcb* GetTcbBase() {
return Libraries::Kernel::g_curthread->tcb;

View File

@ -318,10 +318,12 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
for (User user : UserSettings.GetUserManager().GetValidUsers()) {
auto const user_trophy_file =
Common::FS::GetUserPath(Common::FS::PathType::HomeDir) /
std::to_string(user.user_id) / "trophy" / (npCommId + ".xml");
auto const user_trophy_file = EmulatorSettings.GetHomeDir() /
std::to_string(user.user_id) / "trophy" /
(npCommId + ".xml");
if (!std::filesystem::exists(user_trophy_file)) {
auto temp = user_trophy_file.parent_path();
std::filesystem::create_directories(temp);
std::error_code discard;
std::filesystem::copy_file(trophyDir / "Xml" / "TROPCONF.XML", user_trophy_file,
discard);
@ -533,7 +535,7 @@ void Emulator::Restart(std::filesystem::path eboot_path,
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
#elif defined(__APPLE__) || defined(__linux__)
#elif defined(__APPLE__) || defined(__linux__) || defined(__FreeBSD__)
std::vector<char*> argv;
// Emulator executable

View File

@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/emulator_settings.h"
#include "imgui_translations.h"
namespace ImguiTranslate {
const std::map<u32, std::map<std::string, std::string>> langMap = {
{0, JapaneseMap},
// {1, EnglishUsMap}, - not used
{2, FrenchMap},
{3, SpanishMap},
{4, GermanMap},
{5, ItalianMap},
{6, DutchMap},
{7, PortugesePtMap},
{8, RussianMap},
{9, KoreanMap},
{10, ChineseTraditionalMap},
{11, ChineseSimplifiedMap},
{12, FinnishMap},
{13, SwedishMap},
{14, DanishMap},
{15, NorwegianMap},
{16, PolishMap},
{17, PortugeseBrMap},
// {18, "English (UK)"}, - not used
{19, TurkishMap},
{20, SpanishLatinAmericanMap},
{21, ArabicMap},
{22, FrenchCanadaMap},
{23, CzechMap},
{24, HungarianMap},
{25, GreekMap},
{26, RomanianMap},
{27, ThaiMap},
{28, VietnameseMap},
{29, IndonesianMap},
{30, UkranianMap},
};
std::string tr(std::string input) {
// since we're coding in English
if (EmulatorSettings.GetConsoleLanguage() == 1 || EmulatorSettings.GetConsoleLanguage() == 18)
return input;
const std::map<std::string, std::string> translationTable =
langMap.at(EmulatorSettings.GetConsoleLanguage());
if (!translationTable.contains(input)) {
return input;
}
return translationTable.at(input);
}
} // namespace ImguiTranslate

View File

@ -0,0 +1,136 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <map>
#include <string>
namespace ImguiTranslate {
std::string tr(std::string input);
///////////// ImGui Translation Tables
// disable clang line limits for ease of translation
// clang-format off
const std::map<std::string, std::string> JapaneseMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> FrenchMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> FrenchCanadaMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> SpanishMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> SpanishLatinAmericanMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> GermanMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> ItalianMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> DutchMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> PortugesePtMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> PortugeseBrMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> RussianMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> KoreanMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> ChineseTraditionalMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> ChineseSimplifiedMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> FinnishMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> SwedishMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> DanishMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> NorwegianMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> PolishMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> TurkishMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> ArabicMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> CzechMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> HungarianMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> GreekMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> RomanianMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> ThaiMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> VietnameseMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> IndonesianMap = {
{"Trophy Earned", "Trophy Earned"},
};
const std::map<std::string, std::string> UkranianMap = {
{"Trophy Earned", "Trophy Earned"},
};
// clang-format on
///////////// End ImGui Translation Tables
} // namespace ImguiTranslate

View File

@ -96,7 +96,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controller
UNREACHABLE_MSG("Failed to initialize SDL video subsystem: {}", SDL_GetError());
}
if (!SDL_Init(SDL_INIT_CAMERA)) {
UNREACHABLE_MSG("Failed to initialize SDL camera subsystem: {}", SDL_GetError());
LOG_ERROR(Input, "Failed to initialize SDL camera subsystem: {}", SDL_GetError());
}
SDL_InitSubSystem(SDL_INIT_AUDIO);
@ -141,7 +141,8 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controller
window_info.type = WindowSystemType::Windows;
window_info.render_surface = SDL_GetPointerProperty(SDL_GetWindowProperties(window),
SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL);
#elif defined(SDL_PLATFORM_LINUX)
#elif defined(SDL_PLATFORM_LINUX) || defined(__FreeBSD__)
// SDL doesn't have a platform define for FreeBSD AAAAAAAAAA
if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11") == 0) {
window_info.type = WindowSystemType::X11;
window_info.display_connection = SDL_GetPointerProperty(

View File

@ -7,7 +7,7 @@
#include "common/logging/log.h"
#include "core/emulator_settings.h"
#ifdef __linux__
#ifdef __unix__
#include "common/adaptive_mutex.h"
#else
#include "common/spin_lock.h"

View File

@ -36,8 +36,8 @@
namespace VideoCore {
constexpr size_t PAGE_SIZE = 4_KB;
constexpr size_t PAGE_BITS = 12;
constexpr size_t PM_PAGE_SIZE = 4_KB;
constexpr size_t PM_PAGE_BITS = 12;
struct PageManager::Impl {
struct PageState {
@ -85,7 +85,7 @@ struct PageManager::Impl {
};
static constexpr size_t ADDRESS_BITS = 40;
static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PAGE_BITS);
static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PM_PAGE_BITS);
static constexpr size_t NUM_ADDRESS_LOCKS = NUM_ADDRESS_PAGES / PAGES_PER_LOCK;
inline static Vulkan::Rasterizer* rasterizer;
#ifdef ENABLE_USERFAULTFD
@ -222,8 +222,8 @@ struct PageManager::Impl {
void UpdatePageWatchers(VAddr addr, u64 size) {
RENDERER_TRACE;
size_t page = addr >> PAGE_BITS;
const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
size_t page = addr >> PM_PAGE_BITS;
const u64 page_end = Common::DivCeil(addr + size, PM_PAGE_SIZE);
// Acquire locks for the range of pages
const auto lock_start = locks.begin() + (page / PAGES_PER_LOCK);
@ -239,15 +239,15 @@ struct PageManager::Impl {
if (range_bytes > 0) {
RENDERER_TRACE;
// Perform pending (un)protect action
Protect(range_begin << PAGE_BITS, range_bytes, perms);
Protect(range_begin << PM_PAGE_BITS, range_bytes, perms);
range_bytes = 0;
potential_range_bytes = 0;
}
};
// Iterate requested pages
const u64 aligned_addr = page << PAGE_BITS;
const u64 aligned_end = page_end << PAGE_BITS;
const u64 aligned_addr = page << PM_PAGE_BITS;
const u64 aligned_end = page_end << PM_PAGE_BITS;
if (!rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr)) {
LOG_WARNING(Render,
"Tracking memory region {:#x} - {:#x} which is not fully GPU mapped.",
@ -266,7 +266,7 @@ struct PageManager::Impl {
perms = new_perms;
} else if (range_bytes != 0) {
// If the protection did not change, extend the potential range
potential_range_bytes += PAGE_SIZE;
potential_range_bytes += PM_PAGE_SIZE;
}
// Only start a new range if the page must be (un)protected
@ -274,7 +274,7 @@ struct PageManager::Impl {
if (range_bytes == 0) {
// Start a new potential range
range_begin = page;
potential_range_bytes = PAGE_SIZE;
potential_range_bytes = PM_PAGE_SIZE;
}
// Extend current range up to potential range
range_bytes = potential_range_bytes;
@ -293,12 +293,12 @@ struct PageManager::Impl {
if (start_range.second == end_range.second) {
// if all pages are contiguous, use the regular UpdatePageWatchers
const VAddr start_addr = base_addr + (start_range.first << PAGE_BITS);
const u64 size = (start_range.second - start_range.first) << PAGE_BITS;
const VAddr start_addr = base_addr + (start_range.first << PM_PAGE_BITS);
const u64 size = (start_range.second - start_range.first) << PM_PAGE_BITS;
return UpdatePageWatchers<track, is_read>(start_addr, size);
}
size_t base_page = (base_addr >> PAGE_BITS);
size_t base_page = (base_addr >> PM_PAGE_BITS);
ASSERT(base_page % PAGES_PER_LOCK == 0);
std::scoped_lock lk(locks[base_page / PAGES_PER_LOCK]);
auto perms = cached_pages[base_page + start_range.first].Perms();
@ -310,7 +310,7 @@ struct PageManager::Impl {
if (range_bytes > 0) {
RENDERER_TRACE;
// Perform pending (un)protect action
Protect((range_begin << PAGE_BITS), range_bytes, perms);
Protect((range_begin << PM_PAGE_BITS), range_bytes, perms);
range_bytes = 0;
potential_range_bytes = 0;
}
@ -331,7 +331,7 @@ struct PageManager::Impl {
perms = new_perms;
} else if (range_bytes != 0) {
// If the protection did not change, extend the potential range
potential_range_bytes += PAGE_SIZE;
potential_range_bytes += PM_PAGE_SIZE;
}
// If the page is not being updated, skip it
@ -344,7 +344,7 @@ struct PageManager::Impl {
if (range_bytes == 0) {
// Start a new potential range
range_begin = base_page + page;
potential_range_bytes = PAGE_SIZE;
potential_range_bytes = PM_PAGE_SIZE;
}
// Extend current rango up to potential range
range_bytes = potential_range_bytes;
@ -356,7 +356,7 @@ struct PageManager::Impl {
}
std::array<PageState, NUM_ADDRESS_PAGES> cached_pages{};
#ifdef __linux__
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
using LockType = Common::AdaptiveMutex;
#else
using LockType = Common::SpinLock;

View File

@ -3,6 +3,7 @@
#pragma once
#include <cstddef>
#include <memory>
#include "common/alignment.h"
#include "common/types.h"
@ -15,9 +16,10 @@ class Rasterizer;
namespace VideoCore {
class PageManager {
// PAGE_SIZE and PAGE_BITS conflicts with machine/param.h definitions on freebsd!
// Use the same page size as the tracker.
static constexpr size_t PAGE_BITS = TRACKER_PAGE_BITS;
static constexpr size_t PAGE_SIZE = TRACKER_BYTES_PER_PAGE;
static constexpr size_t PM_PAGE_BITS = TRACKER_PAGE_BITS;
static constexpr size_t PM_PAGE_SIZE = TRACKER_BYTES_PER_PAGE;
// Keep the lock granularity the same as region granularity. (since each regions has
// itself a lock)
@ -43,12 +45,12 @@ public:
/// Returns page aligned address.
static constexpr VAddr GetPageAddr(VAddr addr) {
return Common::AlignDown(addr, PAGE_SIZE);
return Common::AlignDown(addr, PM_PAGE_SIZE);
}
/// Returns address of the next page.
static constexpr VAddr GetNextPageAddr(VAddr addr) {
return Common::AlignUp(addr + 1, PAGE_SIZE);
return Common::AlignUp(addr + 1, PM_PAGE_SIZE);
}
private: