mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2026-04-25 20:25:38 -06:00
Merge branch 'shadps4-emu:main' into ime-fixes-again
This commit is contained in:
commit
a8e0495f4c
3
.gitignore
vendored
3
.gitignore
vendored
@ -418,3 +418,6 @@ FodyWeavers.xsd
|
||||
# JetBrains
|
||||
.idea
|
||||
cmake-build-*
|
||||
|
||||
# Nix Result symlink
|
||||
result
|
||||
|
||||
@ -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)
|
||||
@ -1093,6 +1093,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
|
||||
@ -1139,7 +1141,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")
|
||||
@ -1185,6 +1194,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)
|
||||
@ -1276,4 +1287,4 @@ install(TARGETS shadps4 BUNDLE DESTINATION .)
|
||||
else()
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
12
README.md
12
README.md
@ -150,12 +150,12 @@ The following firmware modules are supported and must be placed in shadPS4's `sy
|
||||
|
||||
<div align="center">
|
||||
|
||||
| Modules | Modules | Modules | Modules |
|
||||
|-------------------------|-------------------------|-------------------------|-------------------------|
|
||||
| libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx | libSceFreeTypeOt.sprx |
|
||||
| libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx | libSceJson2.sprx |
|
||||
| libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx | libSceRtc.sprx |
|
||||
| libSceUlt.sprx | libSceAudiodec.sprx | | |
|
||||
| Modules | Modules | Modules | Modules |
|
||||
|--------------------------|--------------------------|--------------------------|--------------------------|
|
||||
| libSceAudiodec.sprx | libSceCesCs.sprx | libSceFont.sprx | libSceFontFt.sprx |
|
||||
| libSceFreeTypeOt.sprx | libSceJpegDec.sprx | libSceJpegEnc.sprx | libSceJson.sprx |
|
||||
| libSceJson2.sprx | libSceLibcInternal.sprx | libSceNgs2.sprx | libScePngEnc.sprx |
|
||||
| libSceRtc.sprx | libSceSystemGesture.sprx | libSceUlt.sprx | |
|
||||
</div>
|
||||
|
||||
> [!Caution]
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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:
|
||||
|
||||
24
externals/CMakeLists.txt
vendored
24
externals/CMakeLists.txt
vendored
@ -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
27
flake.lock
generated
Normal 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
160
flake.nix
Normal 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];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __linux__
|
||||
#if __unix__
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
|
||||
@ -42,7 +42,8 @@ struct Archive {
|
||||
}
|
||||
|
||||
void Advance(size_t size) {
|
||||
ASSERT(offset + size <= container.size());
|
||||
ASSERT_MSG(offset + size <= container.size(),
|
||||
"Invalid or corrupted deserialization container/shader cache");
|
||||
offset += size;
|
||||
}
|
||||
|
||||
@ -104,7 +105,8 @@ struct Writer {
|
||||
struct Reader {
|
||||
template <typename T>
|
||||
void Read(T* ptr, size_t size) {
|
||||
ASSERT(ar.offset + size <= ar.container.size());
|
||||
ASSERT_MSG(ar.offset + size <= ar.container.size(),
|
||||
"Invalid or corrupted deserialization container/shader cache");
|
||||
std::memcpy(reinterpret_cast<void*>(ptr), ar.CurrPtr(), size);
|
||||
ar.Advance(size);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
#include "common/key_manager.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "core/file_format/npbind.h"
|
||||
#include "core/file_format/trp.h"
|
||||
|
||||
static void DecryptEFSM(std::span<const u8, 16> trophyKey, std::span<const u8, 16> NPcommID,
|
||||
@ -43,8 +42,10 @@ static void hexToBytes(const char* hex, unsigned char* dst) {
|
||||
}
|
||||
}
|
||||
|
||||
bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string titleId) {
|
||||
std::filesystem::path gameSysDir = trophyPath / "sce_sys/trophy/";
|
||||
bool TRP::Extract(const std::filesystem::path& trophyPath, int index, std::string npCommId,
|
||||
const std::filesystem::path& outputPath) {
|
||||
std::filesystem::path gameSysDir =
|
||||
trophyPath / "sce_sys/trophy/" / std::format("trophy{:02d}.trp", index);
|
||||
if (!std::filesystem::exists(gameSysDir)) {
|
||||
LOG_WARNING(Common_Filesystem, "Game trophy directory doesn't exist");
|
||||
return false;
|
||||
@ -61,117 +62,82 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
|
||||
std::array<u8, 16> user_key{};
|
||||
std::copy(user_key_vec.begin(), user_key_vec.end(), user_key.begin());
|
||||
|
||||
// Load npbind.dat using the new class
|
||||
std::filesystem::path npbindPath = trophyPath / "sce_sys/npbind.dat";
|
||||
NPBindFile npbind;
|
||||
if (!npbind.Load(npbindPath.string())) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file");
|
||||
}
|
||||
|
||||
auto npCommIds = npbind.GetNpCommIds();
|
||||
if (npCommIds.empty()) {
|
||||
LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat");
|
||||
}
|
||||
|
||||
bool success = true;
|
||||
int trpFileIndex = 0;
|
||||
|
||||
try {
|
||||
// Process each TRP file in the trophy directory
|
||||
for (const auto& it : std::filesystem::directory_iterator(gameSysDir)) {
|
||||
if (!it.is_regular_file() || it.path().extension() != ".trp") {
|
||||
continue; // Skip non-TRP files
|
||||
}
|
||||
const auto& it = gameSysDir;
|
||||
if (it.extension() != ".trp") {
|
||||
return false;
|
||||
}
|
||||
Common::FS::IOFile file(it, Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get NPCommID for this TRP file (if available)
|
||||
std::string npCommId;
|
||||
if (trpFileIndex < static_cast<int>(npCommIds.size())) {
|
||||
npCommId = npCommIds[trpFileIndex];
|
||||
LOG_DEBUG(Common_Filesystem, "Using NPCommID: {} for {}", npCommId,
|
||||
it.path().filename().string());
|
||||
} else {
|
||||
LOG_WARNING(Common_Filesystem, "No NPCommID found for TRP file index {}",
|
||||
trpFileIndex);
|
||||
}
|
||||
TrpHeader header;
|
||||
if (!file.Read(header)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}", it.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
Common::FS::IOFile file(it.path(), Common::FS::FileAccessMode::Read);
|
||||
if (!file.IsOpen()) {
|
||||
LOG_ERROR(Common_Filesystem, "Unable to open trophy file: {}", it.path().string());
|
||||
if (header.magic != TRP_MAGIC) {
|
||||
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
s64 seekPos = sizeof(TrpHeader);
|
||||
// Create output directories
|
||||
if (!std::filesystem::create_directories(outputPath / "Icons") ||
|
||||
!std::filesystem::create_directories(outputPath / "Xml")) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", npCommId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Process each entry in the TRP file
|
||||
for (int i = 0; i < header.entry_num; i++) {
|
||||
if (!file.Seek(seekPos)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
success = false;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
seekPos += static_cast<s64>(header.entry_size);
|
||||
|
||||
TrpHeader header;
|
||||
if (!file.Read(header)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP header from {}",
|
||||
it.path().string());
|
||||
TrpEntry entry;
|
||||
if (!file.Read(entry)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
|
||||
success = false;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
if (header.magic != TRP_MAGIC) {
|
||||
LOG_ERROR(Common_Filesystem, "Wrong trophy magic number in {}", it.path().string());
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
std::string_view name(entry.entry_name);
|
||||
|
||||
s64 seekPos = sizeof(TrpHeader);
|
||||
std::filesystem::path trpFilesPath(
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / titleId /
|
||||
"TrophyFiles" / it.path().stem());
|
||||
|
||||
// Create output directories
|
||||
if (!std::filesystem::create_directories(trpFilesPath / "Icons") ||
|
||||
!std::filesystem::create_directories(trpFilesPath / "Xml")) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to create output directories for {}", titleId);
|
||||
success = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process each entry in the TRP file
|
||||
for (int i = 0; i < header.entry_num; i++) {
|
||||
if (!file.Seek(seekPos)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek to TRP entry offset");
|
||||
if (entry.flag == ENTRY_FLAG_PNG) {
|
||||
if (!ProcessPngEntry(file, entry, outputPath, name)) {
|
||||
success = false;
|
||||
break;
|
||||
// Continue with next entry
|
||||
}
|
||||
seekPos += static_cast<s64>(header.entry_size);
|
||||
|
||||
TrpEntry entry;
|
||||
if (!file.Read(entry)) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to read TRP entry");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
std::string_view name(entry.entry_name);
|
||||
|
||||
if (entry.flag == ENTRY_FLAG_PNG) {
|
||||
if (!ProcessPngEntry(file, entry, trpFilesPath, name)) {
|
||||
} else if (entry.flag == ENTRY_FLAG_ENCRYPTED_XML) {
|
||||
// Check if we have a valid NPCommID for decryption
|
||||
if (npCommId.size() >= 12 && npCommId[0] == 'N' && npCommId[1] == 'P') {
|
||||
if (!ProcessEncryptedXmlEntry(file, entry, outputPath, name, user_key,
|
||||
npCommId)) {
|
||||
success = false;
|
||||
// Continue with next entry
|
||||
}
|
||||
} else if (entry.flag == ENTRY_FLAG_ENCRYPTED_XML) {
|
||||
// Check if we have a valid NPCommID for decryption
|
||||
if (npCommId.size() >= 12 && npCommId[0] == 'N' && npCommId[1] == 'P') {
|
||||
if (!ProcessEncryptedXmlEntry(file, entry, trpFilesPath, name, user_key,
|
||||
npCommId)) {
|
||||
success = false;
|
||||
// Continue with next entry
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"Skipping encrypted XML entry - invalid NPCommID");
|
||||
// Skip this entry but continue
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(Common_Filesystem, "Unknown entry flag: {} for {}",
|
||||
static_cast<unsigned int>(entry.flag), name);
|
||||
LOG_WARNING(Common_Filesystem,
|
||||
"Skipping encrypted XML entry - invalid NPCommID");
|
||||
// Skip this entry but continue
|
||||
}
|
||||
} else {
|
||||
LOG_DEBUG(Common_Filesystem, "Unknown entry flag: {} for {}",
|
||||
static_cast<unsigned int>(entry.flag), name);
|
||||
}
|
||||
|
||||
trpFileIndex++;
|
||||
}
|
||||
|
||||
} catch (const std::filesystem::filesystem_error& e) {
|
||||
LOG_CRITICAL(Common_Filesystem, "Filesystem error during trophy extraction: {}", e.what());
|
||||
return false;
|
||||
@ -182,7 +148,7 @@ bool TRP::Extract(const std::filesystem::path& trophyPath, const std::string tit
|
||||
|
||||
if (success) {
|
||||
LOG_INFO(Common_Filesystem, "Successfully extracted {} trophy files for {}", trpFileIndex,
|
||||
titleId);
|
||||
npCommId);
|
||||
}
|
||||
|
||||
return success;
|
||||
|
||||
@ -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
|
||||
@ -36,7 +36,8 @@ class TRP {
|
||||
public:
|
||||
TRP();
|
||||
~TRP();
|
||||
bool Extract(const std::filesystem::path& trophyPath, const std::string titleId);
|
||||
bool Extract(const std::filesystem::path& trophyPath, int index, std::string npCommId,
|
||||
const std::filesystem::path& outputPath);
|
||||
|
||||
private:
|
||||
bool ProcessPngEntry(Common::FS::IOFile& file, const TrpEntry& entry,
|
||||
@ -45,9 +46,6 @@ private:
|
||||
const std::filesystem::path& outputPath, std::string_view name,
|
||||
const std::array<u8, 16>& user_key, const std::string& npCommId);
|
||||
|
||||
std::vector<u8> NPcommID = std::vector<u8>(12);
|
||||
std::array<u8, 16> np_comm_id{};
|
||||
std::array<u8, 16> esfmIv{};
|
||||
std::filesystem::path trpFilesPath;
|
||||
static constexpr int iv_len = 16;
|
||||
};
|
||||
|
||||
@ -42,7 +42,7 @@ void MntPoints::UnmountAll() {
|
||||
}
|
||||
|
||||
std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_read_only,
|
||||
bool force_base_path) {
|
||||
HostPathType path_type) {
|
||||
// Evil games like Turok2 pass double slashes e.g /app0//game.kpf
|
||||
std::string corrected_path(path);
|
||||
size_t pos = corrected_path.find("//");
|
||||
@ -80,8 +80,24 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
|
||||
}
|
||||
patch_path /= rel_path;
|
||||
|
||||
std::filesystem::path mods_path = mount->host_path;
|
||||
mods_path += "-mods";
|
||||
mods_path /= rel_path;
|
||||
|
||||
if (path_type == HostPathType::Mod) {
|
||||
return mods_path;
|
||||
} else if (path_type == HostPathType::Patch) {
|
||||
return patch_path;
|
||||
}
|
||||
|
||||
if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) &&
|
||||
!force_base_path && !ignore_game_patches && std::filesystem::exists(patch_path)) {
|
||||
path_type != HostPathType::Base && std::filesystem::exists(mods_path)) {
|
||||
return mods_path;
|
||||
}
|
||||
|
||||
if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) &&
|
||||
path_type != HostPathType::Base && !ignore_game_patches &&
|
||||
std::filesystem::exists(patch_path)) {
|
||||
return patch_path;
|
||||
}
|
||||
|
||||
@ -95,7 +111,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,44 +120,46 @@ 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);
|
||||
};
|
||||
|
||||
if (!force_base_path && !ignore_game_patches) {
|
||||
if (path_type != HostPathType::Base && !ignore_game_patches) {
|
||||
if (const auto path = search(patch_path)) {
|
||||
return *path;
|
||||
}
|
||||
@ -158,34 +176,54 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
|
||||
// TODO: Does not handle mount points inside mount points.
|
||||
void MntPoints::IterateDirectory(std::string_view guest_directory,
|
||||
const IterateDirectoryCallback& callback) {
|
||||
const auto base_path = GetHostPath(guest_directory, nullptr, true);
|
||||
const auto patch_path = GetHostPath(guest_directory, nullptr, false);
|
||||
// Only need to consider patch path if it exists and does not resolve to the same as base.
|
||||
const auto apply_patch = base_path != patch_path && std::filesystem::exists(patch_path);
|
||||
const auto base_path = GetHostPath(guest_directory, nullptr, HostPathType::Base);
|
||||
|
||||
// Forces path types so as not to resolve to base path
|
||||
const auto patch_path = GetHostPath(guest_directory, nullptr, HostPathType::Patch);
|
||||
const auto mod_path = GetHostPath(guest_directory, nullptr, HostPathType::Mod);
|
||||
|
||||
// Prepend entries for . and .., as both are treated as files on PS4.
|
||||
callback(base_path / ".", false);
|
||||
callback(base_path / "..", false);
|
||||
|
||||
// Pass 1: Any files that existed in the base directory, using patch directory if needed.
|
||||
// Pass 1: Any files that existed in the base directory, using mod/patch directory if needed.
|
||||
if (std::filesystem::exists(base_path)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(base_path)) {
|
||||
if (apply_patch) {
|
||||
const auto patch_entry_path = patch_path / entry.path().filename();
|
||||
if (std::filesystem::exists(patch_entry_path)) {
|
||||
callback(patch_entry_path, !std::filesystem::is_directory(patch_entry_path));
|
||||
continue;
|
||||
}
|
||||
const auto mod_entry_path = mod_path / entry.path().filename();
|
||||
const auto patch_entry_path = patch_path / entry.path().filename();
|
||||
if (std::filesystem::exists(mod_entry_path)) {
|
||||
callback(mod_entry_path, !std::filesystem::is_directory(mod_entry_path));
|
||||
continue;
|
||||
} else if (std::filesystem::exists(patch_entry_path)) {
|
||||
callback(patch_entry_path, !std::filesystem::is_directory(patch_entry_path));
|
||||
continue;
|
||||
}
|
||||
callback(entry.path(), !entry.is_directory());
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Any files that exist only in the patch directory.
|
||||
if (apply_patch) {
|
||||
if (std::filesystem::exists(patch_path)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(patch_path)) {
|
||||
const auto base_entry_path = base_path / entry.path().filename();
|
||||
if (!std::filesystem::exists(base_entry_path)) {
|
||||
const auto mod_entry_path = mod_path / entry.path().filename();
|
||||
if (std::filesystem::exists(mod_entry_path)) {
|
||||
callback(mod_entry_path, !std::filesystem::is_directory(mod_entry_path));
|
||||
continue;
|
||||
}
|
||||
callback(entry.path(), !entry.is_directory());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 3: Any files that exist only in the mod directory (confirmed this can be valid)
|
||||
if (std::filesystem::exists(mod_path)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(mod_path)) {
|
||||
const auto base_entry_path = base_path / entry.path().filename();
|
||||
const auto patch_entry_path = patch_path / entry.path().filename();
|
||||
if (!std::filesystem::exists(base_entry_path) &&
|
||||
!std::filesystem::exists(patch_entry_path)) {
|
||||
callback(entry.path(), !entry.is_directory());
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,13 @@ public:
|
||||
bool read_only;
|
||||
};
|
||||
|
||||
enum class HostPathType {
|
||||
Default, // Prioritizes Mod, then patch, then base
|
||||
Base,
|
||||
Patch,
|
||||
Mod
|
||||
};
|
||||
|
||||
explicit MntPoints() = default;
|
||||
~MntPoints() = default;
|
||||
|
||||
@ -45,7 +52,8 @@ public:
|
||||
void UnmountAll();
|
||||
|
||||
std::filesystem::path GetHostPath(std::string_view guest_directory,
|
||||
bool* is_read_only = nullptr, bool force_base_path = false);
|
||||
bool* is_read_only = nullptr,
|
||||
HostPathType host_path = HostPathType::Default);
|
||||
using IterateDirectoryCallback =
|
||||
std::function<void(const std::filesystem::path& host_path, bool is_file)>;
|
||||
void IterateDirectory(std::string_view guest_directory,
|
||||
|
||||
@ -211,13 +211,6 @@ void IPC::InputLoop() {
|
||||
} else if (cmd == "RELOAD_INPUTS") {
|
||||
std::string config = next_str();
|
||||
Input::ParseInputConfig(config);
|
||||
} else if (cmd == "SET_ACTIVE_CONTROLLER") {
|
||||
std::string active_controller = next_str();
|
||||
GamepadSelect::SetSelectedGamepad(active_controller);
|
||||
SDL_Event checkGamepad;
|
||||
SDL_memset(&checkGamepad, 0, sizeof(checkGamepad));
|
||||
checkGamepad.type = SDL_EVENT_CHANGE_CONTROLLER;
|
||||
SDL_PushEvent(&checkGamepad);
|
||||
} else {
|
||||
std::cerr << ";UNKNOWN CMD: " << cmd << std::endl;
|
||||
}
|
||||
|
||||
@ -298,6 +298,126 @@ s32 PS4_SYSV_ABI sceKernelGetAppInfo(s32 pid, OrbisKernelAppInfo* app_info) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
// Nominally: long sysconf(int name);
|
||||
u64 PS4_SYSV_ABI posix_sysconf(s32 name) {
|
||||
switch (name) {
|
||||
case 0:
|
||||
return 0x20000;
|
||||
case POSIX_SC_ARG_MAX:
|
||||
return 0x588bc000;
|
||||
case POSIX_SC_CHILD_MAX:
|
||||
return 0x64;
|
||||
case POSIX_SC_CLK_TCK:
|
||||
return 0x20;
|
||||
case POSIX_SC_NGROUPS_MAX:
|
||||
return 0x644;
|
||||
case POSIX_SC_OPEN_MAX:
|
||||
return -0x1;
|
||||
case POSIX_SC_JOB_CONTROL:
|
||||
return 0x6;
|
||||
case POSIX_SC_SAVED_IDS:
|
||||
return 0x1;
|
||||
case POSIX_SC_VERSION:
|
||||
return 0x1;
|
||||
case POSIX_SC_BC_BASE_MAX:
|
||||
return 0x31069;
|
||||
case POSIX_SC_BC_DIM_MAX:
|
||||
return -0x1;
|
||||
case POSIX_SC_BC_SCALE_MAX:
|
||||
return 0x31069;
|
||||
case POSIX_SC_BC_STRING_MAX:
|
||||
return 0x31069;
|
||||
case POSIX_SC_COLL_WEIGHTS_MAX:
|
||||
return -0x1;
|
||||
case POSIX_SC_EXPR_NEST_MAX:
|
||||
return -0x1;
|
||||
case POSIX_SC_LINE_MAX:
|
||||
return 0x31069;
|
||||
case POSIX_SC_RE_DUP_MAX:
|
||||
return 0x31069;
|
||||
case POSIX_SC_2_VERSION:
|
||||
return 0x31069;
|
||||
case POSIX_SC_2_C_BIND:
|
||||
return 0x31069;
|
||||
case POSIX_SC_2_C_DEV:
|
||||
return 0x31069;
|
||||
case POSIX_SC_2_CHAR_TERM:
|
||||
return 0x31069;
|
||||
case POSIX_SC_2_FORT_DEV:
|
||||
return 0x31069;
|
||||
case POSIX_SC_2_FORT_RUN:
|
||||
return 0x31069;
|
||||
case POSIX_SC_2_LOCALEDEF:
|
||||
return -0x1;
|
||||
case POSIX_SC_2_SW_DEV:
|
||||
return -0x1;
|
||||
case POSIX_SC_2_UPE:
|
||||
return 0x0;
|
||||
case POSIX_SC_STREAM_MAX:
|
||||
return 0x7fffffff;
|
||||
case POSIX_SC_TZNAME_MAX:
|
||||
return -0x1;
|
||||
case POSIX_SC_ASYNCHRONOUS_IO:
|
||||
return 0x8000;
|
||||
case POSIX_SC_MAPPED_FILES:
|
||||
return 0x31069;
|
||||
case POSIX_SC_MEMLOCK:
|
||||
return 0x4000;
|
||||
case POSIX_SC_MEMLOCK_RANGE:
|
||||
return 0x1e;
|
||||
case POSIX_SC_MEMORY_PROTECTION:
|
||||
return 0x100;
|
||||
case POSIX_SC_MESSAGE_PASSING:
|
||||
return 0x7fffffff;
|
||||
case POSIX_SC_PRIORITIZED_IO:
|
||||
return -0x1;
|
||||
case POSIX_SC_PRIORITY_SCHEDULING:
|
||||
return -0x1;
|
||||
case POSIX_SC_REALTIME_SIGNALS:
|
||||
return 0x63;
|
||||
case POSIX_SC_SEMAPHORES:
|
||||
return 0x800;
|
||||
case POSIX_SC_FSYNC:
|
||||
return 0x63;
|
||||
case POSIX_SC_SHARED_MEMORY_OBJECTS:
|
||||
return 0x3e8;
|
||||
case POSIX_SC_SYNCHRONIZED_IO:
|
||||
return 0x2;
|
||||
case POSIX_SC_THREAD_ATTR_STACKSIZE:
|
||||
return 0x1;
|
||||
case POSIX_SC_THREAD_CPUTIME:
|
||||
return 0x1;
|
||||
case POSIX_SC_THREAD_DESTRUCTOR_ITERATIONS:
|
||||
return 0x48000;
|
||||
case POSIX_SC_THREAD_KEYS_MAX:
|
||||
return 0x1a078630b2dd7;
|
||||
case POSIX_SC_THREAD_PRIO_INHERIT:
|
||||
return -0x1;
|
||||
case POSIX_SC_THREAD_PRIO_PROTECT:
|
||||
return -0x1;
|
||||
case POSIX_SC_THREAD_PRIORITY_SCHEDULING:
|
||||
return 0x2bc;
|
||||
case POSIX_SC_THREAD_PROCESS_SHARED:
|
||||
return 0x2bc;
|
||||
case POSIX_SC_THREAD_SAFE_FUNCTIONS:
|
||||
return 0x1;
|
||||
case POSIX_SC_THREAD_SPORADIC_SERVER:
|
||||
return -0x1;
|
||||
case POSIX_SC_THREAD_STACK_MIN:
|
||||
return 0x1;
|
||||
case POSIX_SC_THREAD_THREADS_MAX:
|
||||
return 0x1;
|
||||
case POSIX_SC_TIMEOUTS:
|
||||
return -0x1;
|
||||
// Manually specified
|
||||
case POSIX_SC_PAGESIZE:
|
||||
return posix_getpagesize();
|
||||
default:
|
||||
LOG_ERROR(Lib_Kernel, "unhandled {}", name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
service_thread = std::jthread{KernelServiceThread};
|
||||
|
||||
@ -327,6 +447,10 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("9BcDykPmo1I", "libkernel", 1, "libkernel", __Error);
|
||||
LIB_FUNCTION("k+AXqu2-eBc", "libkernel", 1, "libkernel", posix_getpagesize);
|
||||
LIB_FUNCTION("k+AXqu2-eBc", "libScePosix", 1, "libkernel", posix_getpagesize);
|
||||
|
||||
LIB_FUNCTION("mkawd0NA9ts", "libkernel", 1, "libkernel", posix_sysconf);
|
||||
LIB_FUNCTION("mkawd0NA9ts", "libScePosix", 1, "libkernel", posix_sysconf);
|
||||
|
||||
LIB_FUNCTION("NWtTN10cJzE", "libSceLibcInternalExt", 1, "libSceLibcInternal",
|
||||
sceLibcHeapGetTraceInfo);
|
||||
|
||||
|
||||
@ -82,4 +82,126 @@ struct OrbisKernelAppInfo {
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
constexpr u32 POSIX_SC_ARG_MAX = 1;
|
||||
constexpr u32 POSIX_SC_CHILD_MAX = 2;
|
||||
constexpr u32 POSIX_SC_CLK_TCK = 3;
|
||||
constexpr u32 POSIX_SC_NGROUPS_MAX = 4;
|
||||
constexpr u32 POSIX_SC_OPEN_MAX = 5;
|
||||
constexpr u32 POSIX_SC_JOB_CONTROL = 6;
|
||||
constexpr u32 POSIX_SC_SAVED_IDS = 7;
|
||||
constexpr u32 POSIX_SC_VERSION = 8;
|
||||
constexpr u32 POSIX_SC_BC_BASE_MAX = 9;
|
||||
constexpr u32 POSIX_SC_BC_DIM_MAX = 10;
|
||||
constexpr u32 POSIX_SC_BC_SCALE_MAX = 11;
|
||||
constexpr u32 POSIX_SC_BC_STRING_MAX = 12;
|
||||
constexpr u32 POSIX_SC_COLL_WEIGHTS_MAX = 13;
|
||||
constexpr u32 POSIX_SC_EXPR_NEST_MAX = 14;
|
||||
constexpr u32 POSIX_SC_LINE_MAX = 15;
|
||||
constexpr u32 POSIX_SC_RE_DUP_MAX = 16;
|
||||
constexpr u32 POSIX_SC_2_VERSION = 17;
|
||||
constexpr u32 POSIX_SC_2_C_BIND = 18;
|
||||
constexpr u32 POSIX_SC_2_C_DEV = 19;
|
||||
constexpr u32 POSIX_SC_2_CHAR_TERM = 20;
|
||||
constexpr u32 POSIX_SC_2_FORT_DEV = 21;
|
||||
constexpr u32 POSIX_SC_2_FORT_RUN = 22;
|
||||
constexpr u32 POSIX_SC_2_LOCALEDEF = 23;
|
||||
constexpr u32 POSIX_SC_2_SW_DEV = 24;
|
||||
constexpr u32 POSIX_SC_2_UPE = 25;
|
||||
constexpr u32 POSIX_SC_STREAM_MAX = 26;
|
||||
constexpr u32 POSIX_SC_TZNAME_MAX = 27;
|
||||
constexpr u32 POSIX_SC_ASYNCHRONOUS_IO = 28;
|
||||
constexpr u32 POSIX_SC_MAPPED_FILES = 29;
|
||||
constexpr u32 POSIX_SC_MEMLOCK = 30;
|
||||
constexpr u32 POSIX_SC_MEMLOCK_RANGE = 31;
|
||||
constexpr u32 POSIX_SC_MEMORY_PROTECTION = 32;
|
||||
constexpr u32 POSIX_SC_MESSAGE_PASSING = 33;
|
||||
constexpr u32 POSIX_SC_PRIORITIZED_IO = 34;
|
||||
constexpr u32 POSIX_SC_PRIORITY_SCHEDULING = 35;
|
||||
constexpr u32 POSIX_SC_REALTIME_SIGNALS = 36;
|
||||
constexpr u32 POSIX_SC_SEMAPHORES = 37;
|
||||
constexpr u32 POSIX_SC_FSYNC = 38;
|
||||
constexpr u32 POSIX_SC_SHARED_MEMORY_OBJECTS = 39;
|
||||
constexpr u32 POSIX_SC_SYNCHRONIZED_IO = 40;
|
||||
constexpr u32 POSIX_SC_TIMERS = 41;
|
||||
constexpr u32 POSIX_SC_AIO_LISTIO_MAX = 42;
|
||||
constexpr u32 POSIX_SC_AIO_MAX = 43;
|
||||
constexpr u32 POSIX_SC_AIO_PRIO_DELTA_MAX = 44;
|
||||
constexpr u32 POSIX_SC_DELAYTIMER_MAX = 45;
|
||||
constexpr u32 POSIX_SC_MQ_OPEN_MAX = 46;
|
||||
constexpr u32 POSIX_SC_PAGESIZE = 47;
|
||||
constexpr u32 POSIX_SC_RTSIG_MAX = 48;
|
||||
constexpr u32 POSIX_SC_SEM_NSEMS_MAX = 49;
|
||||
constexpr u32 POSIX_SC_SEM_VALUE_MAX = 50;
|
||||
constexpr u32 POSIX_SC_SIGQUEUE_MAX = 51;
|
||||
constexpr u32 POSIX_SC_TIMER_MAX = 52;
|
||||
constexpr u32 POSIX_SC_2_PBS = 59;
|
||||
constexpr u32 POSIX_SC_2_PBS_ACCOUNTING = 60;
|
||||
constexpr u32 POSIX_SC_2_PBS_CHECKPOINT = 61;
|
||||
constexpr u32 POSIX_SC_2_PBS_LOCATE = 62;
|
||||
constexpr u32 POSIX_SC_2_PBS_MESSAGE = 63;
|
||||
constexpr u32 POSIX_SC_2_PBS_TRACK = 64;
|
||||
constexpr u32 POSIX_SC_ADVISORY_INFO = 65;
|
||||
constexpr u32 POSIX_SC_BARRIERS = 66;
|
||||
constexpr u32 POSIX_SC_CLOCK_SELECTION = 67;
|
||||
constexpr u32 POSIX_SC_CPUTIME = 68;
|
||||
constexpr u32 POSIX_SC_FILE_LOCKING = 69;
|
||||
constexpr u32 POSIX_SC_GETGR_R_SIZE_MAX = 70;
|
||||
constexpr u32 POSIX_SC_GETPW_R_SIZE_MAX = 71;
|
||||
constexpr u32 POSIX_SC_HOST_NAME_MAX = 72;
|
||||
constexpr u32 POSIX_SC_LOGIN_NAME_MAX = 73;
|
||||
constexpr u32 POSIX_SC_MONOTONIC_CLOCK = 74;
|
||||
constexpr u32 POSIX_SC_MQ_PRIO_MAX = 75;
|
||||
constexpr u32 POSIX_SC_READER_WRITER_LOCKS = 76;
|
||||
constexpr u32 POSIX_SC_REGEXP = 77;
|
||||
constexpr u32 POSIX_SC_SHELL = 78;
|
||||
constexpr u32 POSIX_SC_SPAWN = 79;
|
||||
constexpr u32 POSIX_SC_SPIN_LOCKS = 80;
|
||||
constexpr u32 POSIX_SC_SPORADIC_SERVER = 81;
|
||||
constexpr u32 POSIX_SC_THREAD_ATTR_STACKADDR = 82;
|
||||
constexpr u32 POSIX_SC_THREAD_ATTR_STACKSIZE = 83;
|
||||
constexpr u32 POSIX_SC_THREAD_CPUTIME = 84;
|
||||
constexpr u32 POSIX_SC_THREAD_DESTRUCTOR_ITERATIONS = 85;
|
||||
constexpr u32 POSIX_SC_THREAD_KEYS_MAX = 86;
|
||||
constexpr u32 POSIX_SC_THREAD_PRIO_INHERIT = 87;
|
||||
constexpr u32 POSIX_SC_THREAD_PRIO_PROTECT = 88;
|
||||
constexpr u32 POSIX_SC_THREAD_PRIORITY_SCHEDULING = 89;
|
||||
constexpr u32 POSIX_SC_THREAD_PROCESS_SHARED = 90;
|
||||
constexpr u32 POSIX_SC_THREAD_SAFE_FUNCTIONS = 91;
|
||||
constexpr u32 POSIX_SC_THREAD_SPORADIC_SERVER = 92;
|
||||
constexpr u32 POSIX_SC_THREAD_STACK_MIN = 93;
|
||||
constexpr u32 POSIX_SC_THREAD_THREADS_MAX = 94;
|
||||
constexpr u32 POSIX_SC_TIMEOUTS = 95;
|
||||
constexpr u32 POSIX_SC_THREADS = 96;
|
||||
constexpr u32 POSIX_SC_TRACE = 97;
|
||||
constexpr u32 POSIX_SC_TRACE_EVENT_FILTER = 98;
|
||||
constexpr u32 POSIX_SC_TRACE_INHERIT = 99;
|
||||
constexpr u32 POSIX_SC_TRACE_LOG = 100;
|
||||
constexpr u32 POSIX_SC_TTY_NAME_MAX = 101;
|
||||
constexpr u32 POSIX_SC_TYPED_MEMORY_OBJECTS = 102;
|
||||
constexpr u32 POSIX_SC_V6_ILP32_OFF32 = 103;
|
||||
constexpr u32 POSIX_SC_V6_ILP32_OFFBIG = 104;
|
||||
constexpr u32 POSIX_SC_V6_LP64_OFF64 = 105;
|
||||
constexpr u32 POSIX_SC_V6_LPBIG_OFFBIG = 106;
|
||||
constexpr u32 POSIX_SC_IPV6 = 118;
|
||||
constexpr u32 POSIX_SC_RAW_SOCKETS = 119;
|
||||
constexpr u32 POSIX_SC_SYMLOOP_MAX = 120;
|
||||
constexpr u32 POSIX_SC_ATEXIT_MAX = 107;
|
||||
constexpr u32 POSIX_SC_IOV_MAX = 56;
|
||||
constexpr u32 POSIX_SC_XOPEN_CRYPT = 108;
|
||||
constexpr u32 POSIX_SC_XOPEN_ENH_I18N = 109;
|
||||
constexpr u32 POSIX_SC_XOPEN_LEGACY = 110;
|
||||
constexpr u32 POSIX_SC_XOPEN_REALTIME = 111;
|
||||
constexpr u32 POSIX_SC_XOPEN_REALTIME_THREADS = 112;
|
||||
constexpr u32 POSIX_SC_XOPEN_SHM = 113;
|
||||
constexpr u32 POSIX_SC_XOPEN_STREAMS = 114;
|
||||
constexpr u32 POSIX_SC_XOPEN_UNIX = 115;
|
||||
constexpr u32 POSIX_SC_XOPEN_VERSION = 116;
|
||||
constexpr u32 POSIX_SC_XOPEN_XCU_VERSION = 117;
|
||||
constexpr u32 POSIX_SC_NPROCESSORS_CONF = 57;
|
||||
constexpr u32 POSIX_SC_NPROCESSORS_ONLN = 58;
|
||||
constexpr u32 POSIX_SC_CPUSET_SIZE = 122;
|
||||
constexpr u32 POSIX_SC_UEXTERR_MAXLEN = 123;
|
||||
constexpr u32 POSIX_SC_NSIG = 124;
|
||||
constexpr u32 POSIX_SC_PHYS_PAGES = 121;
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -339,7 +339,11 @@ s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot) {
|
||||
Core::MemoryManager* memory_manager = Core::Memory::Instance();
|
||||
Core::MemoryProt protection_flags = static_cast<Core::MemoryProt>(prot);
|
||||
|
||||
return memory_manager->Protect(aligned_addr, aligned_size, protection_flags);
|
||||
s32 result = memory_manager->Protect(aligned_addr, aligned_size, protection_flags);
|
||||
if (result == ORBIS_OK) {
|
||||
memory_manager->InvalidateMemory(aligned_addr, aligned_size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_mprotect(const void* addr, u64 size, s32 prot) {
|
||||
@ -370,6 +374,7 @@ s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s3
|
||||
s32 result = memory_manager->Protect(aligned_addr, aligned_size, protection_flags);
|
||||
if (result == ORBIS_OK) {
|
||||
memory_manager->SetDirectMemoryType(aligned_addr, aligned_size, mtype);
|
||||
memory_manager->InvalidateMemory(aligned_addr, aligned_size);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -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];
|
||||
@ -286,6 +308,28 @@ bool PS4_SYSV_ABI posix_sigisemptyset(Sigset* s) {
|
||||
return s->bits[0] == 0 && s->bits[1] == 0;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_sigalstack(const OrbisKernelExceptionHandlerStack* ss,
|
||||
OrbisKernelExceptionHandlerStack* old_ss) {
|
||||
#ifdef __unix__
|
||||
stack_t native_ss{};
|
||||
if (ss) {
|
||||
native_ss.ss_sp = ss->ss_sp;
|
||||
native_ss.ss_flags = ss->ss_flags;
|
||||
native_ss.ss_size = ss->ss_size;
|
||||
}
|
||||
stack_t native_old_ss{};
|
||||
sigaltstack(&native_ss, &native_old_ss);
|
||||
if (old_ss) {
|
||||
old_ss->ss_sp = native_old_ss.ss_sp;
|
||||
old_ss->ss_flags = native_old_ss.ss_flags;
|
||||
old_ss->ss_size = native_old_ss.ss_size;
|
||||
}
|
||||
#else
|
||||
LOG_ERROR(Lib_Kernel, "(stubbed)");
|
||||
#endif
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI posix_sigaction(s32 sig, Sigaction* act, Sigaction* oact) {
|
||||
if (sig < 1 || sig > 128 || sig == POSIX_SIGTHR || sig == POSIX_SIGKILL ||
|
||||
sig == POSIX_SIGSTOP) {
|
||||
@ -303,7 +347,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;
|
||||
@ -473,9 +517,12 @@ void RegisterException(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("KiJEPEWRyUY", "libkernel", 1, "libkernel", posix_sigaction);
|
||||
LIB_FUNCTION("+F7C-hdk7+E", "libkernel", 1, "libkernel", posix_sigemptyset);
|
||||
LIB_FUNCTION("yH-uQW3LbX0", "libkernel", 1, "libkernel", posix_pthread_kill);
|
||||
LIB_FUNCTION("sHziAegVp74", "libkernel", 1, "libkernel", posix_sigalstack);
|
||||
|
||||
LIB_FUNCTION("KiJEPEWRyUY", "libScePosix", 1, "libkernel", posix_sigaction);
|
||||
LIB_FUNCTION("+F7C-hdk7+E", "libScePosix", 1, "libkernel", posix_sigemptyset);
|
||||
LIB_FUNCTION("yH-uQW3LbX0", "libScePosix", 1, "libkernel", posix_pthread_kill);
|
||||
LIB_FUNCTION("sHziAegVp74", "libScePosix", 1, "libkernel", posix_sigalstack);
|
||||
}
|
||||
|
||||
} // namespace Libraries::Kernel
|
||||
|
||||
@ -12,6 +12,11 @@ class SymbolsResolver;
|
||||
namespace Libraries::Kernel {
|
||||
|
||||
using OrbisKernelExceptionHandler = PS4_SYSV_ABI void (*)(int, void*);
|
||||
struct OrbisKernelExceptionHandlerStack {
|
||||
void* ss_sp;
|
||||
int ss_flags;
|
||||
size_t ss_size;
|
||||
};
|
||||
|
||||
constexpr s32 POSIX_SIGHUP = 1;
|
||||
constexpr s32 POSIX_SIGINT = 2;
|
||||
@ -47,7 +52,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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -63,7 +63,6 @@
|
||||
#include "core/libraries/system/posix.h"
|
||||
#include "core/libraries/system/systemservice.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/libraries/system_gesture/system_gesture.h"
|
||||
#include "core/libraries/ulobjmgr/ulobjmgr.h"
|
||||
#include "core/libraries/usbd/usbd.h"
|
||||
#include "core/libraries/videodec/videodec.h"
|
||||
@ -120,7 +119,6 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) {
|
||||
Libraries::Random::RegisterLib(sym);
|
||||
Libraries::Usbd::RegisterLib(sym);
|
||||
Libraries::Pad::RegisterLib(sym);
|
||||
Libraries::SystemGesture::RegisterLib(sym);
|
||||
Libraries::Ajm::RegisterLib(sym);
|
||||
Libraries::ErrorDialog::RegisterLib(sym);
|
||||
Libraries::ImeDialog::RegisterLib(sym);
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <variant>
|
||||
|
||||
#include <core/emulator_settings.h>
|
||||
#include "common/config.h"
|
||||
#include <core/user_settings.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
@ -632,7 +632,8 @@ s32 PS4_SYSV_ABI sceNpGetNpId(Libraries::UserService::OrbisUserServiceUserId use
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
memset(np_id, 0, sizeof(OrbisNpId));
|
||||
strncpy(np_id->handle.data, Config::getUserName().c_str(), sizeof(np_id->handle.data));
|
||||
strncpy(np_id->handle.data, UserManagement.GetDefaultUser().user_name.c_str(),
|
||||
sizeof(np_id->handle.data));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -646,7 +647,8 @@ s32 PS4_SYSV_ABI sceNpGetOnlineId(Libraries::UserService::OrbisUserServiceUserId
|
||||
return ORBIS_NP_ERROR_SIGNED_OUT;
|
||||
}
|
||||
memset(online_id, 0, sizeof(OrbisNpOnlineId));
|
||||
strncpy(online_id->data, Config::getUserName().c_str(), sizeof(online_id->data));
|
||||
strncpy(online_id->data, UserManagement.GetDefaultUser().user_name.c_str(),
|
||||
sizeof(online_id->data));
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -14,10 +14,7 @@ namespace Libraries::Np::NpPartner {
|
||||
static bool g_library_init = false;
|
||||
std::mutex g_library_mutex{};
|
||||
|
||||
/**
|
||||
* Terminates the library
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_A4CC5784DA33517F() {
|
||||
s32 PS4_SYSV_ABI sceNpEAAccessTerminate() {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
if (!g_library_init) {
|
||||
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
|
||||
@ -27,10 +24,7 @@ s32 PS4_SYSV_ABI Func_A4CC5784DA33517F() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aborts requests started by Func_F8E9DB52CD425743
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_A507D84D91F39CC7() {
|
||||
s32 PS4_SYSV_ABI sceNpHasEAAccessSubscriptionAbortRequest() {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
if (!g_library_init) {
|
||||
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
|
||||
@ -39,20 +33,15 @@ s32 PS4_SYSV_ABI Func_A507D84D91F39CC7() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the library
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_EC2C48E74FF19429() {
|
||||
s32 PS4_SYSV_ABI sceNpEAAccessInitialize() {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
g_library_init = true;
|
||||
// Also retrieves and sends compiled SDK version to server.
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an NP request to determine if the user has a subscription to EA's services.
|
||||
*/
|
||||
s32 PS4_SYSV_ABI Func_F8E9DB52CD425743(UserService::OrbisUserServiceUserId user_id, bool* result) {
|
||||
s32 PS4_SYSV_ABI sceNpHasEAAccessSubscription(UserService::OrbisUserServiceUserId user_id,
|
||||
bool* result) {
|
||||
LOG_ERROR(Lib_NpPartner, "(STUBBED) called");
|
||||
if (!g_library_init) {
|
||||
return ORBIS_NP_PARTNER_ERROR_NOT_INITIALIZED;
|
||||
@ -71,13 +60,13 @@ s32 PS4_SYSV_ABI Func_F8E9DB52CD425743(UserService::OrbisUserServiceUserId user_
|
||||
|
||||
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
|
||||
LIB_FUNCTION("pMxXhNozUX8", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_A4CC5784DA33517F);
|
||||
sceNpEAAccessTerminate);
|
||||
LIB_FUNCTION("pQfYTZHznMc", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_A507D84D91F39CC7);
|
||||
sceNpHasEAAccessSubscriptionAbortRequest);
|
||||
LIB_FUNCTION("7CxI50-xlCk", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_EC2C48E74FF19429);
|
||||
sceNpEAAccessInitialize);
|
||||
LIB_FUNCTION("+OnbUs1CV0M", "libSceNpPartner001", 1, "libSceNpPartner001",
|
||||
Func_F8E9DB52CD425743);
|
||||
sceNpHasEAAccessSubscription);
|
||||
};
|
||||
|
||||
} // namespace Libraries::Np::NpPartner
|
||||
@ -1,22 +1,118 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <unordered_map>
|
||||
#include <pugixml.hpp>
|
||||
|
||||
#include "common/elf_info.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/slot_vector.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/np/np_error.h"
|
||||
#include "core/libraries/np/np_trophy.h"
|
||||
#include "core/libraries/np/np_trophy_error.h"
|
||||
#include "core/libraries/np/trophy_ui.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Libraries::Np::NpTrophy {
|
||||
|
||||
std::string game_serial;
|
||||
// PS4 system language IDs map directly to TROP00.XML .. TROP30.XML.
|
||||
// Index = OrbisSystemServiceParamId language value reported by the system.
|
||||
// clang-format off
|
||||
static constexpr std::array<std::string_view, 31> s_language_xml_names = {
|
||||
"TROP_00.XML", // 00 Japanese
|
||||
"TROP_01.XML", // 01 English (US)
|
||||
"TROP_02.XML", // 02 French
|
||||
"TROP_03.XML", // 03 Spanish (ES)
|
||||
"TROP_04.XML", // 04 German
|
||||
"TROP_05.XML", // 05 Italian
|
||||
"TROP_06.XML", // 06 Dutch
|
||||
"TROP_07.XML", // 07 Portuguese (PT)
|
||||
"TROP_08.XML", // 08 Russian
|
||||
"TROP_09.XML", // 09 Korean
|
||||
"TROP_10.XML", // 10 Traditional Chinese
|
||||
"TROP_11.XML", // 11 Simplified Chinese
|
||||
"TROP_12.XML", // 12 Finnish
|
||||
"TROP_13.XML", // 13 Swedish
|
||||
"TROP_14.XML", // 14 Danish
|
||||
"TROP_15.XML", // 15 Norwegian
|
||||
"TROP_16.XML", // 16 Polish
|
||||
"TROP_17.XML", // 17 Portuguese (BR)
|
||||
"TROP_18.XML", // 18 English (GB)
|
||||
"TROP_19.XML", // 19 Turkish
|
||||
"TROP_20.XML", // 20 Spanish (LA)
|
||||
"TROP_21.XML", // 21 Arabic
|
||||
"TROP_22.XML", // 22 French (CA)
|
||||
"TROP_23.XML", // 23 Czech
|
||||
"TROP_24.XML", // 24 Hungarian
|
||||
"TROP_25.XML", // 25 Greek
|
||||
"TROP_26.XML", // 26 Romanian
|
||||
"TROP_27.XML", // 27 Thai
|
||||
"TROP_28.XML", // 28 Vietnamese
|
||||
"TROP_29.XML", // 29 Indonesian
|
||||
"TROP_30.XML", // 30 Unkrainian
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Returns the best available trophy XML path for the current system language.
|
||||
// Resolution order:
|
||||
// 1. TROP_XX.XML for the active system language (e.g. TROP01.XML for English)
|
||||
// 2. TROP.XML (master / language-neutral fallback)
|
||||
static std::filesystem::path GetTrophyXmlPath(const std::filesystem::path& xml_dir,
|
||||
int system_language) {
|
||||
// Try the exact language file first.
|
||||
if (system_language >= 0 && system_language < static_cast<int>(s_language_xml_names.size())) {
|
||||
auto lang_path = xml_dir / s_language_xml_names[system_language];
|
||||
if (std::filesystem::exists(lang_path)) {
|
||||
return lang_path;
|
||||
}
|
||||
}
|
||||
// Final fallback: master TROP.XML (always present).
|
||||
return xml_dir / "TROP.XML";
|
||||
}
|
||||
|
||||
static void ApplyUnlockToXmlFile(const std::filesystem::path& xml_path, OrbisNpTrophyId trophyId,
|
||||
u64 trophyTimestamp, bool unlock_platinum,
|
||||
OrbisNpTrophyId platinumId, u64 platinumTimestamp) {
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_file(xml_path.native().c_str())) {
|
||||
LOG_WARNING(Lib_NpTrophy, "ApplyUnlock: failed to load {}", xml_path.string());
|
||||
return;
|
||||
}
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
for (pugi::xml_node& node : trophyconf.children()) {
|
||||
if (std::string_view(node.name()) != "trophy") {
|
||||
continue;
|
||||
}
|
||||
int id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
|
||||
auto set_unlock = [&](u64 ts) {
|
||||
if (node.attribute("unlockstate").empty()) {
|
||||
node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
const auto ts_str = std::to_string(ts);
|
||||
if (node.attribute("timestamp").empty()) {
|
||||
node.append_attribute("timestamp") = ts_str.c_str();
|
||||
} else {
|
||||
node.attribute("timestamp").set_value(ts_str.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
if (id == trophyId) {
|
||||
set_unlock(trophyTimestamp);
|
||||
} else if (unlock_platinum && id == platinumId) {
|
||||
set_unlock(platinumTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
doc.save_file(xml_path.native().c_str());
|
||||
}
|
||||
|
||||
static constexpr auto MaxTrophyHandles = 4u;
|
||||
static constexpr auto MaxTrophyContexts = 8u;
|
||||
@ -30,6 +126,11 @@ struct ContextKeyHash {
|
||||
|
||||
struct TrophyContext {
|
||||
u32 context_id;
|
||||
bool registered = false;
|
||||
std::filesystem::path trophy_xml_path; // resolved once at CreateContext
|
||||
std::filesystem::path xml_dir; // .../Xml/
|
||||
std::filesystem::path xml_save_file; // The actual file for tracking progress per-user.
|
||||
std::filesystem::path icons_dir; // .../Icons/
|
||||
};
|
||||
static Common::SlotVector<OrbisNpTrophyHandle> trophy_handles{};
|
||||
static Common::SlotVector<ContextKey> trophy_contexts{};
|
||||
@ -94,66 +195,10 @@ OrbisNpTrophyGrade GetTrophyGradeFromChar(char trophyType) {
|
||||
}
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfo() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
|
||||
Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
uint32_t service_label, u64 options) {
|
||||
ASSERT(options == 0ull);
|
||||
if (!context) {
|
||||
if (!context || options != 0ull) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
@ -169,7 +214,20 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context,
|
||||
const auto ctx_id = trophy_contexts.insert(user_id, service_label);
|
||||
|
||||
*context = ctx_id.index + 1;
|
||||
contexts_internal[key].context_id = *context;
|
||||
|
||||
auto& ctx = contexts_internal[key];
|
||||
ctx.context_id = *context;
|
||||
|
||||
// Resolve and cache all paths once so callers never recompute them.
|
||||
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 =
|
||||
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());
|
||||
|
||||
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id,
|
||||
service_label);
|
||||
|
||||
@ -206,6 +264,10 @@ int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
if (!trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
trophy_contexts.erase(contextId);
|
||||
contexts_internal.erase(contextkey);
|
||||
@ -251,12 +313,10 @@ int PS4_SYSV_ABI sceNpTrophyGetGameIcon(OrbisNpTrophyContext context, OrbisNpTro
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto icon_file = trophy_dir / trophy_folder / "Icons" / "ICON0.PNG";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
|
||||
auto icon_file = ctx.icons_dir / "ICON0.PNG";
|
||||
|
||||
Common::FS::IOFile icon(icon_file, Common::FS::FileAccessMode::Read);
|
||||
if (!icon.IsOpen()) {
|
||||
@ -304,12 +364,11 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
const auto& trophy_save_file = ctx.xml_save_file;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -336,7 +395,18 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
|
||||
|
||||
if (node_name == "group")
|
||||
game_info.num_groups++;
|
||||
}
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
if (node_name == "trophy") {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
@ -368,8 +438,9 @@ int PS4_SYSV_ABI sceNpTrophyGetGameInfo(OrbisNpTrophyContext context, OrbisNpTro
|
||||
data->unlocked_silver = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlocked_bronze = game_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progress_percentage = 100;
|
||||
data->progress_percentage = (game_info.num_trophies > 0)
|
||||
? (game_info.unlocked_trophies * 100u) / game_info.num_trophies
|
||||
: 0;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -411,12 +482,10 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -450,7 +519,18 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
|
||||
|
||||
details->group_id = groupId;
|
||||
data->group_id = groupId;
|
||||
}
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
if (node_name == "trophy") {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
@ -484,15 +564,84 @@ int PS4_SYSV_ABI sceNpTrophyGetGroupInfo(OrbisNpTrophyContext context, OrbisNpTr
|
||||
data->unlocked_silver = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_SILVER];
|
||||
data->unlocked_bronze = group_info.unlocked_trophies_by_rarity[ORBIS_NP_TROPHY_GRADE_BRONZE];
|
||||
|
||||
// maybe this should be 1 instead of 100?
|
||||
data->progress_percentage = 100;
|
||||
data->progress_percentage =
|
||||
(group_info.num_trophies > 0)
|
||||
? (group_info.unlocked_trophies * 100u) / group_info.num_trophies
|
||||
: 0;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyGetTrophyIcon(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyId trophyId, void* buffer, u64* size) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
if (size == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (trophyId < 0 || trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
|
||||
// Check that the trophy is unlocked and icons are only available for earned trophies.
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_file(ctx.xml_save_file.native().c_str())) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy XML: {}", ctx.xml_save_file.string());
|
||||
return ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
bool unlocked = false;
|
||||
bool found = false;
|
||||
for (const pugi::xml_node& node : doc.child("trophyconf").children()) {
|
||||
if (std::string_view(node.name()) != "trophy")
|
||||
continue;
|
||||
if (node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID) == trophyId) {
|
||||
found = true;
|
||||
unlocked = node.attribute("unlockstate").as_bool();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (!unlocked)
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_NOT_UNLOCKED;
|
||||
|
||||
const std::string icon_name = fmt::format("TROP{:03d}.PNG", trophyId);
|
||||
const auto icon_path = ctx.icons_dir / icon_name;
|
||||
|
||||
Common::FS::IOFile icon(icon_path, Common::FS::FileAccessMode::Read);
|
||||
if (!icon.IsOpen()) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to open trophy icon: {}", icon_path.string());
|
||||
return ORBIS_NP_TROPHY_ERROR_ICON_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (buffer != nullptr) {
|
||||
ReadFile(icon, buffer, *size);
|
||||
} else {
|
||||
*size = icon.GetSize();
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -507,7 +656,7 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
if (trophyId >= 127)
|
||||
if (trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (details == nullptr || data == nullptr)
|
||||
@ -522,12 +671,10 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -545,12 +692,34 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
if (node_name == "trophy") {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
if (current_trophy_id == trophyId) {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
std::string_view current_trophy_name = node.child("name").text().as_string();
|
||||
std::string_view current_trophy_description =
|
||||
node.child("detail").text().as_string();
|
||||
|
||||
strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
|
||||
strncpy(details->description, current_trophy_description.data(),
|
||||
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
|
||||
if (node_name == "trophy") {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
if (current_trophy_id == trophyId) {
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_grade = node.attribute("ttype").value();
|
||||
|
||||
uint64_t current_trophy_timestamp = node.attribute("timestamp").as_ullong();
|
||||
int current_trophy_groupid = node.attribute("gid").as_int(-1);
|
||||
bool current_trophy_hidden = node.attribute("hidden").as_bool();
|
||||
@ -560,10 +729,6 @@ int PS4_SYSV_ABI sceNpTrophyGetTrophyInfo(OrbisNpTrophyContext context, OrbisNpT
|
||||
details->group_id = current_trophy_groupid;
|
||||
details->hidden = current_trophy_hidden;
|
||||
|
||||
strncpy(details->name, current_trophy_name.data(), ORBIS_NP_TROPHY_NAME_MAX_SIZE);
|
||||
strncpy(details->description, current_trophy_description.data(),
|
||||
ORBIS_NP_TROPHY_DESCR_MAX_SIZE);
|
||||
|
||||
data->trophy_id = trophyId;
|
||||
data->unlocked = current_trophy_unlockstate;
|
||||
data->timestamp.tick = current_trophy_timestamp;
|
||||
@ -579,29 +744,34 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
||||
OrbisNpTrophyFlagArray* flags, u32* count) {
|
||||
LOG_INFO(Lib_NpTrophy, "called");
|
||||
|
||||
if (flags == nullptr || count == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
if (flags == nullptr || count == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size()) {
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& trophy_file = ctx.xml_save_file;
|
||||
|
||||
ORBIS_NP_TROPHY_FLAG_ZERO(flags);
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
@ -622,10 +792,9 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
||||
|
||||
if (node_name == "trophy") {
|
||||
num_trophies++;
|
||||
}
|
||||
|
||||
if (current_trophy_unlockstate) {
|
||||
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
|
||||
if (current_trophy_unlockstate) {
|
||||
ORBIS_NP_TROPHY_FLAG_SET(current_trophy_id, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,6 +802,200 @@ s32 PS4_SYSV_ABI sceNpTrophyGetTrophyUnlockState(OrbisNpTrophyContext context,
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
|
||||
OrbisNpTrophyHandle handle, uint64_t options) {
|
||||
if (options != 0ull)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
auto& ctx = contexts_internal[contextkey];
|
||||
|
||||
if (ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_ALREADY_REGISTERED;
|
||||
|
||||
if (!std::filesystem::exists(ctx.trophy_xml_path))
|
||||
return ORBIS_NP_TROPHY_ERROR_TITLE_CONF_NOT_INSTALLED;
|
||||
|
||||
ctx.registered = true;
|
||||
LOG_INFO(Lib_NpTrophy, "Context {} registered", context);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) {
|
||||
LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId);
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
if (trophyId >= ORBIS_NP_TROPHY_NUM_MAX)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
if (platinumId == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size() || !trophy_contexts.is_allocated(contextId)) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size() ||
|
||||
!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
const auto& ctx = contexts_internal[contextkey];
|
||||
if (!ctx.registered)
|
||||
return ORBIS_NP_TROPHY_ERROR_NOT_REGISTERED;
|
||||
const auto& xml_dir = ctx.xml_dir;
|
||||
const auto& trophy_file = ctx.trophy_xml_path;
|
||||
|
||||
pugi::xml_document save_doc;
|
||||
pugi::xml_parse_result save_result = save_doc.load_file(ctx.xml_save_file.native().c_str());
|
||||
|
||||
if (!save_result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse user trophy xml : {}", save_result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
auto save_trophyconf = save_doc.child("trophyconf");
|
||||
for (const pugi::xml_node& node : save_trophyconf.children()) {
|
||||
std::string_view node_name = node.name();
|
||||
if (std::string_view(node.name()) != "trophy")
|
||||
continue;
|
||||
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
|
||||
if (current_trophy_id == trophyId) {
|
||||
if (current_trophy_unlockstate) {
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
|
||||
return ORBIS_NP_TROPHY_ERROR_TITLE_NOT_FOUND;
|
||||
}
|
||||
|
||||
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
|
||||
|
||||
int num_trophies = 0;
|
||||
int num_trophies_unlocked = 0;
|
||||
pugi::xml_node platinum_node;
|
||||
|
||||
// Outputs filled during the scan.
|
||||
bool trophy_found = false;
|
||||
const char* trophy_name = "";
|
||||
std::string_view trophy_type;
|
||||
std::filesystem::path trophy_icon_path;
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
|
||||
for (pugi::xml_node& node : trophyconf.children()) {
|
||||
if (std::string_view(node.name()) != "trophy")
|
||||
continue;
|
||||
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
std::string_view current_trophy_type = node.attribute("ttype").value();
|
||||
|
||||
if (current_trophy_type == "P") {
|
||||
platinum_node = node;
|
||||
if (trophyId == current_trophy_id) {
|
||||
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
|
||||
num_trophies++;
|
||||
if (current_trophy_unlockstate) {
|
||||
num_trophies_unlocked++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_trophy_id == trophyId) {
|
||||
trophy_found = true;
|
||||
trophy_name = node.child("name").text().as_string();
|
||||
trophy_type = current_trophy_type;
|
||||
|
||||
const std::string icon_file = fmt::format("TROP{:03d}.PNG", current_trophy_id);
|
||||
trophy_icon_path = ctx.icons_dir / icon_file;
|
||||
}
|
||||
}
|
||||
|
||||
if (!trophy_found)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
|
||||
// Capture timestamps once so every file gets the exact same value.
|
||||
const auto now_secs = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
const u64 trophy_timestamp = static_cast<u64>(now_secs);
|
||||
|
||||
// Decide platinum.
|
||||
bool unlock_platinum = false;
|
||||
OrbisNpTrophyId platinum_id = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
|
||||
u64 platinum_timestamp = 0;
|
||||
const char* platinum_name = "";
|
||||
std::filesystem::path platinum_icon_path;
|
||||
|
||||
if (!platinum_node.attribute("unlockstate").as_bool()) {
|
||||
if ((num_trophies - 1) == num_trophies_unlocked) {
|
||||
unlock_platinum = true;
|
||||
platinum_id = platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
platinum_timestamp = trophy_timestamp; // same second is fine
|
||||
platinum_name = platinum_node.child("name").text().as_string();
|
||||
|
||||
const std::string plat_icon_file = fmt::format("TROP{:03d}.PNG", platinum_id);
|
||||
platinum_icon_path = ctx.icons_dir / plat_icon_file;
|
||||
|
||||
*platinumId = platinum_id;
|
||||
}
|
||||
}
|
||||
|
||||
// Queue UI notifications (only once, using the primary XML's strings).
|
||||
AddTrophyToQueue(trophy_icon_path, trophy_name, trophy_type);
|
||||
if (unlock_platinum) {
|
||||
AddTrophyToQueue(platinum_icon_path, platinum_name, "P");
|
||||
}
|
||||
|
||||
ApplyUnlockToXmlFile(ctx.xml_save_file, trophyId, trophy_timestamp, unlock_platinum,
|
||||
platinum_id, platinum_timestamp);
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy {} successfully saved.", trophyId);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyGroupArrayGetNum() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -698,19 +1061,6 @@ int PS4_SYSV_ABI sceNpTrophyNumInfoGetTotal() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyRegisterContext(OrbisNpTrophyContext context,
|
||||
OrbisNpTrophyHandle handle, uint64_t options) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophySetInfoGetTrophyFlagArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -942,147 +1292,58 @@ int PS4_SYSV_ABI sceNpTrophySystemSetDbgParamInt() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyUnlockTrophy(OrbisNpTrophyContext context, OrbisNpTrophyHandle handle,
|
||||
OrbisNpTrophyId trophyId, OrbisNpTrophyId* platinumId) {
|
||||
LOG_INFO(Lib_NpTrophy, "Unlocking trophy id {}", trophyId);
|
||||
int PS4_SYSV_ABI sceNpTrophyAbortHandle(OrbisNpTrophyHandle handle) {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
int PS4_SYSV_ABI sceNpTrophyCaptureScreenshot() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (trophyId >= 127)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyFlagArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (platinumId == nullptr)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_ARGUMENT;
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupArray() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context - 1;
|
||||
if (contextId.index >= trophy_contexts.size()) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
char trophy_folder[9];
|
||||
snprintf(trophy_folder, sizeof(trophy_folder), "trophy%02d", contextkey.second);
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyGroupDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
const auto trophy_dir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / game_serial / "TrophyFiles";
|
||||
auto trophy_file = trophy_dir / trophy_folder / "Xml" / "TROP.XML";
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfo() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_parse_result result = doc.load_file(trophy_file.native().c_str());
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetInfoInGroup() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Failed to parse trophy xml : {}", result.description());
|
||||
return ORBIS_OK;
|
||||
}
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophySetVersion() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
*platinumId = ORBIS_NP_TROPHY_INVALID_TROPHY_ID;
|
||||
|
||||
int num_trophies = 0;
|
||||
int num_trophies_unlocked = 0;
|
||||
pugi::xml_node platinum_node;
|
||||
|
||||
auto trophyconf = doc.child("trophyconf");
|
||||
|
||||
for (pugi::xml_node& node : trophyconf.children()) {
|
||||
int current_trophy_id = node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
bool current_trophy_unlockstate = node.attribute("unlockstate").as_bool();
|
||||
const char* current_trophy_name = node.child("name").text().as_string();
|
||||
std::string_view current_trophy_description = node.child("detail").text().as_string();
|
||||
std::string_view current_trophy_type = node.attribute("ttype").value();
|
||||
|
||||
if (current_trophy_type == "P") {
|
||||
platinum_node = node;
|
||||
if (trophyId == current_trophy_id) {
|
||||
return ORBIS_NP_TROPHY_ERROR_PLATINUM_CANNOT_UNLOCK;
|
||||
}
|
||||
}
|
||||
|
||||
if (std::string_view(node.name()) == "trophy") {
|
||||
if (node.attribute("pid").as_int(-1) != ORBIS_NP_TROPHY_INVALID_TROPHY_ID) {
|
||||
num_trophies++;
|
||||
if (current_trophy_unlockstate) {
|
||||
num_trophies_unlocked++;
|
||||
}
|
||||
}
|
||||
|
||||
if (current_trophy_id == trophyId) {
|
||||
if (current_trophy_unlockstate) {
|
||||
LOG_INFO(Lib_NpTrophy, "Trophy already unlocked");
|
||||
return ORBIS_NP_TROPHY_ERROR_TROPHY_ALREADY_UNLOCKED;
|
||||
} else {
|
||||
if (node.attribute("unlockstate").empty()) {
|
||||
node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
|
||||
auto trophyTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
if (node.attribute("timestamp").empty()) {
|
||||
node.append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp).c_str();
|
||||
} else {
|
||||
node.attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp).c_str());
|
||||
}
|
||||
|
||||
std::string trophy_icon_file = "TROP";
|
||||
trophy_icon_file.append(node.attribute("id").value());
|
||||
trophy_icon_file.append(".PNG");
|
||||
|
||||
std::filesystem::path current_icon_path =
|
||||
trophy_dir / trophy_folder / "Icons" / trophy_icon_file;
|
||||
|
||||
AddTrophyToQueue(current_icon_path, current_trophy_name, current_trophy_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!platinum_node.attribute("unlockstate").as_bool()) {
|
||||
if ((num_trophies - 1) == num_trophies_unlocked) {
|
||||
if (platinum_node.attribute("unlockstate").empty()) {
|
||||
platinum_node.append_attribute("unlockstate") = "true";
|
||||
} else {
|
||||
platinum_node.attribute("unlockstate").set_value("true");
|
||||
}
|
||||
|
||||
auto trophyTimestamp = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
|
||||
if (platinum_node.attribute("timestamp").empty()) {
|
||||
platinum_node.append_attribute("timestamp") =
|
||||
std::to_string(trophyTimestamp).c_str();
|
||||
} else {
|
||||
platinum_node.attribute("timestamp")
|
||||
.set_value(std::to_string(trophyTimestamp).c_str());
|
||||
}
|
||||
|
||||
int platinum_trophy_id =
|
||||
platinum_node.attribute("id").as_int(ORBIS_NP_TROPHY_INVALID_TROPHY_ID);
|
||||
const char* platinum_trophy_name = platinum_node.child("name").text().as_string();
|
||||
|
||||
std::string platinum_icon_file = "TROP";
|
||||
platinum_icon_file.append(platinum_node.attribute("id").value());
|
||||
platinum_icon_file.append(".PNG");
|
||||
|
||||
std::filesystem::path platinum_icon_path =
|
||||
trophy_dir / trophy_folder / "Icons" / platinum_icon_file;
|
||||
|
||||
*platinumId = platinum_trophy_id;
|
||||
AddTrophyToQueue(platinum_icon_path, platinum_trophy_name, "P");
|
||||
}
|
||||
}
|
||||
|
||||
doc.save_file((trophy_dir / trophy_folder / "Xml" / "TROP.XML").native().c_str());
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigGetTrophyTitleDetails() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyConfigHasGroupFeature() {
|
||||
LOG_ERROR(Lib_NpTrophy, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,6 @@ class SymbolsResolver;
|
||||
|
||||
namespace Libraries::Np::NpTrophy {
|
||||
|
||||
extern std::string game_serial;
|
||||
|
||||
constexpr int ORBIS_NP_TROPHY_FLAG_SETSIZE = 128;
|
||||
constexpr int ORBIS_NP_TROPHY_FLAG_BITS_SHIFT = 5;
|
||||
|
||||
|
||||
@ -1,20 +1,24 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/pad/pad_errors.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "input/controller.h"
|
||||
#include "pad.h"
|
||||
|
||||
namespace Libraries::Pad {
|
||||
|
||||
using Input::GameController;
|
||||
using Input::GameControllers;
|
||||
using namespace Libraries::UserService;
|
||||
|
||||
static bool g_initialized = false;
|
||||
static bool g_opened = false;
|
||||
static std::unordered_map<OrbisUserServiceUserId, s32> user_id_pad_handle_map{};
|
||||
static constexpr s32 tv_remote_handle = 5;
|
||||
|
||||
int PS4_SYSV_ABI scePadClose(s32 handle) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
@ -30,8 +34,8 @@ int PS4_SYSV_ABI scePadDeviceClassGetExtendedInformation(
|
||||
s32 handle, OrbisPadDeviceClassExtendedInformation* pExtInfo) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
std::memset(pExtInfo, 0, sizeof(OrbisPadDeviceClassExtendedInformation));
|
||||
if (Config::getUseSpecialPad()) {
|
||||
pExtInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
pExtInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -107,9 +111,9 @@ int PS4_SYSV_ABI scePadGetControllerInformation(s32 handle, OrbisPadControllerIn
|
||||
return ORBIS_OK;
|
||||
}
|
||||
pInfo->connected = true;
|
||||
if (Config::getUseSpecialPad()) {
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
pInfo->connectionType = ORBIS_PAD_PORT_TYPE_SPECIAL;
|
||||
pInfo->deviceClass = (OrbisPadDeviceClass)Config::getSpecialPadClass();
|
||||
pInfo->deviceClass = (OrbisPadDeviceClass)EmulatorSettings.GetSpecialPadClass();
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -156,11 +160,16 @@ int PS4_SYSV_ABI scePadGetHandle(Libraries::UserService::OrbisUserServiceUserId
|
||||
if (!g_initialized) {
|
||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (userId == -1 || !g_opened) {
|
||||
if (userId == -1) {
|
||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
}
|
||||
LOG_DEBUG(Lib_Pad, "(DUMMY) called");
|
||||
return 1;
|
||||
auto it = user_id_pad_handle_map.find(userId);
|
||||
if (it == user_id_pad_handle_map.end()) {
|
||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
}
|
||||
s32 pad_handle = it->second;
|
||||
LOG_DEBUG(Lib_Pad, "called, userid: {}, out pad handle: {}", userId, pad_handle);
|
||||
return pad_handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadGetIdleCount() {
|
||||
@ -168,8 +177,18 @@ int PS4_SYSV_ABI scePadGetIdleCount() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadGetInfo() {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI scePadGetInfo(OrbisPadInfo* data) {
|
||||
LOG_WARNING(Lib_Pad, "(DUMMY) called");
|
||||
if (!data) {
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
auto col = controllers[0]->GetLightBarRGB();
|
||||
std::memset(data, 0, sizeof(OrbisPadInfo));
|
||||
data->unk1 = 0x1;
|
||||
data->pad_handle = 1;
|
||||
data->unk3 = 0x00000101;
|
||||
data->colour = col.r + (col.g << 8) + (col.b << 16);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -254,34 +273,61 @@ int PS4_SYSV_ABI scePadOpen(Libraries::UserService::OrbisUserServiceUserId userI
|
||||
if (!g_initialized) {
|
||||
return ORBIS_PAD_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
if (userId == -1) {
|
||||
return ORBIS_PAD_ERROR_DEVICE_NO_HANDLE;
|
||||
if (userId < 0) {
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
|
||||
}
|
||||
if (Config::getUseSpecialPad()) {
|
||||
if (userId == ORBIS_USER_SERVICE_USER_ID_SYSTEM) {
|
||||
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
|
||||
LOG_INFO(Lib_Pad, "Opened a TV remote device");
|
||||
user_id_pad_handle_map[ORBIS_USER_SERVICE_USER_ID_SYSTEM] = tv_remote_handle;
|
||||
return tv_remote_handle;
|
||||
}
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_INVALID_USER;
|
||||
}
|
||||
if (type == ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL) {
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
} else {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
|
||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
LOG_INFO(Lib_Pad, "(DUMMY) called user_id = {} type = {} index = {}", userId, type, index);
|
||||
g_opened = true;
|
||||
scePadResetLightBar(userId);
|
||||
scePadResetOrientation(userId);
|
||||
return 1; // dummy
|
||||
auto u = UserManagement.GetUserByID(userId);
|
||||
if (!u) {
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
|
||||
}
|
||||
s32 pad_handle = u->player_index;
|
||||
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
|
||||
index, pad_handle);
|
||||
scePadResetLightBar(pad_handle);
|
||||
scePadResetOrientation(pad_handle);
|
||||
user_id_pad_handle_map[userId] = pad_handle;
|
||||
return pad_handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt(Libraries::UserService::OrbisUserServiceUserId userId, s32 type,
|
||||
s32 index, const OrbisPadOpenExtParam* pParam) {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
if (Config::getUseSpecialPad()) {
|
||||
if (EmulatorSettings.IsUsingSpecialPad()) {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_SPECIAL)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
} else {
|
||||
if (type != ORBIS_PAD_PORT_TYPE_STANDARD && type != ORBIS_PAD_PORT_TYPE_REMOTE_CONTROL)
|
||||
return ORBIS_PAD_ERROR_DEVICE_NOT_CONNECTED;
|
||||
}
|
||||
return 1; // dummy
|
||||
auto u = UserManagement.GetUserByID(userId);
|
||||
if (!u) {
|
||||
return ORBIS_DEVICE_SERVICE_ERROR_USER_NOT_LOGIN;
|
||||
}
|
||||
s32 pad_handle = u->player_index;
|
||||
LOG_INFO(Lib_Pad, "called user_id = {} type = {} index = {}, pad_handle = {}", userId, type,
|
||||
index, pad_handle);
|
||||
scePadResetLightBar(pad_handle);
|
||||
scePadResetOrientation(pad_handle);
|
||||
user_id_pad_handle_map[userId] = pad_handle;
|
||||
return pad_handle;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadOpenExt2() {
|
||||
@ -294,8 +340,8 @@ int PS4_SYSV_ABI scePadOutputReport() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num, bool connected,
|
||||
u32 connected_count) {
|
||||
int ProcessStates(s32 handle, OrbisPadData* pData, Input::GameController& controller,
|
||||
Input::State* states, s32 num, bool connected, u32 connected_count) {
|
||||
if (!connected) {
|
||||
pData[0] = {};
|
||||
pData[0].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
@ -319,73 +365,75 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
|
||||
pData[i].angularVelocity.z = states[i].angularVelocity.z;
|
||||
pData[i].orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
if (engine && handle == 1) {
|
||||
const auto gyro_poll_rate = engine->GetAccelPollRate();
|
||||
if (gyro_poll_rate != 0.0f) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
now - controller->GetLastUpdate())
|
||||
.count() /
|
||||
1000000.0f;
|
||||
controller->SetLastUpdate(now);
|
||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
|
||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
|
||||
deltaTime, lastOrientation, outputOrientation);
|
||||
pData[i].orientation = outputOrientation;
|
||||
controller->SetLastOrientation(outputOrientation);
|
||||
}
|
||||
const auto gyro_poll_rate = controller.accel_poll_rate;
|
||||
if (gyro_poll_rate != 0.0f) {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
||||
now - controller.GetLastUpdate())
|
||||
.count() /
|
||||
1000000.0f;
|
||||
controller.SetLastUpdate(now);
|
||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller.GetLastOrientation();
|
||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
GameControllers::CalculateOrientation(pData->acceleration, pData->angularVelocity,
|
||||
deltaTime, lastOrientation, outputOrientation);
|
||||
pData[i].orientation = outputOrientation;
|
||||
controller.SetLastOrientation(outputOrientation);
|
||||
}
|
||||
pData[i].touchData.touchNum =
|
||||
(states[i].touchpad[0].state ? 1 : 0) + (states[i].touchpad[1].state ? 1 : 0);
|
||||
|
||||
if (handle == 1) {
|
||||
if (controller->GetTouchCount() >= 127) {
|
||||
controller->SetTouchCount(0);
|
||||
if (controller.GetTouchCount() >= 127) {
|
||||
controller.SetTouchCount(0);
|
||||
}
|
||||
|
||||
if (controller->GetSecondaryTouchCount() >= 127) {
|
||||
controller->SetSecondaryTouchCount(0);
|
||||
if (controller.GetSecondaryTouchCount() >= 127) {
|
||||
controller.SetSecondaryTouchCount(0);
|
||||
}
|
||||
|
||||
if (pData->touchData.touchNum == 1 && controller->GetPreviousTouchNum() == 0) {
|
||||
controller->SetTouchCount(controller->GetTouchCount() + 1);
|
||||
controller->SetSecondaryTouchCount(controller->GetTouchCount());
|
||||
} else if (pData->touchData.touchNum == 2 && controller->GetPreviousTouchNum() == 1) {
|
||||
controller->SetSecondaryTouchCount(controller->GetSecondaryTouchCount() + 1);
|
||||
} else if (pData->touchData.touchNum == 0 && controller->GetPreviousTouchNum() > 0) {
|
||||
if (controller->GetTouchCount() < controller->GetSecondaryTouchCount()) {
|
||||
controller->SetTouchCount(controller->GetSecondaryTouchCount());
|
||||
if (pData->touchData.touchNum == 1 && controller.GetPreviousTouchNum() == 0) {
|
||||
controller.SetTouchCount(controller.GetTouchCount() + 1);
|
||||
controller.SetSecondaryTouchCount(controller.GetTouchCount());
|
||||
} else if (pData->touchData.touchNum == 2 && controller.GetPreviousTouchNum() == 1) {
|
||||
controller.SetSecondaryTouchCount(controller.GetSecondaryTouchCount() + 1);
|
||||
} else if (pData->touchData.touchNum == 0 && controller.GetPreviousTouchNum() > 0) {
|
||||
if (controller.GetTouchCount() < controller.GetSecondaryTouchCount()) {
|
||||
controller.SetTouchCount(controller.GetSecondaryTouchCount());
|
||||
} else {
|
||||
if (controller->WasSecondaryTouchReset()) {
|
||||
controller->SetTouchCount(controller->GetSecondaryTouchCount());
|
||||
controller->UnsetSecondaryTouchResetBool();
|
||||
if (controller.WasSecondaryTouchReset()) {
|
||||
controller.SetTouchCount(controller.GetSecondaryTouchCount());
|
||||
controller.UnsetSecondaryTouchResetBool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
controller->SetPreviousTouchNum(pData->touchData.touchNum);
|
||||
controller.SetPreviousTouchNum(pData->touchData.touchNum);
|
||||
|
||||
if (pData->touchData.touchNum == 1) {
|
||||
states[i].touchpad[0].ID = controller->GetTouchCount();
|
||||
states[i].touchpad[0].ID = controller.GetTouchCount();
|
||||
states[i].touchpad[1].ID = 0;
|
||||
} else if (pData->touchData.touchNum == 2) {
|
||||
states[i].touchpad[0].ID = controller->GetTouchCount();
|
||||
states[i].touchpad[1].ID = controller->GetSecondaryTouchCount();
|
||||
states[i].touchpad[0].ID = controller.GetTouchCount();
|
||||
states[i].touchpad[1].ID = controller.GetSecondaryTouchCount();
|
||||
}
|
||||
} else {
|
||||
states[i].touchpad[0].ID = 1;
|
||||
states[i].touchpad[1].ID = 2;
|
||||
}
|
||||
|
||||
pData[i].touchData.touch[0].x = states[i].touchpad[0].x;
|
||||
pData[i].touchData.touch[0].y = states[i].touchpad[0].y;
|
||||
pData[i].touchData.touch[0].id = states[i].touchpad[0].ID;
|
||||
pData[i].touchData.touch[1].x = states[i].touchpad[1].x;
|
||||
pData[i].touchData.touch[1].y = states[i].touchpad[1].y;
|
||||
pData[i].touchData.touch[1].id = states[i].touchpad[1].ID;
|
||||
if (!states[i].touchpad[0].state && states[i].touchpad[1].state) {
|
||||
pData[i].touchData.touch[0].x = states[i].touchpad[1].x;
|
||||
pData[i].touchData.touch[0].y = states[i].touchpad[1].y;
|
||||
pData[i].touchData.touch[0].id = states[i].touchpad[1].ID;
|
||||
} else {
|
||||
pData[i].touchData.touch[0].x = states[i].touchpad[0].x;
|
||||
pData[i].touchData.touch[0].y = states[i].touchpad[0].y;
|
||||
pData[i].touchData.touch[0].id = states[i].touchpad[0].ID;
|
||||
pData[i].touchData.touch[1].x = states[i].touchpad[1].x;
|
||||
pData[i].touchData.touch[1].y = states[i].touchpad[1].y;
|
||||
pData[i].touchData.touch[1].id = states[i].touchpad[1].ID;
|
||||
}
|
||||
pData[i].connected = connected;
|
||||
pData[i].timestamp = states[i].time;
|
||||
pData[i].connectedCount = connected_count;
|
||||
@ -397,16 +445,18 @@ int ProcessStates(s32 handle, OrbisPadData* pData, Input::State* states, s32 num
|
||||
|
||||
int PS4_SYSV_ABI scePadRead(s32 handle, OrbisPadData* pData, s32 num) {
|
||||
LOG_TRACE(Lib_Pad, "called");
|
||||
if (handle < 1) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
std::vector<Input::State> states(64);
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
int ret_num = controller->ReadStates(states.data(), num, &connected, &connected_count);
|
||||
return ProcessStates(handle, pData, states.data(), ret_num, connected, connected_count);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
auto& controller = *controllers[*controller_id];
|
||||
int ret_num = controller.ReadStates(states.data(), num, &connected, &connected_count);
|
||||
return ProcessStates(handle, pData, controller, states.data(), ret_num, connected,
|
||||
connected_count);
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadReadBlasterForTracker() {
|
||||
@ -430,17 +480,18 @@ int PS4_SYSV_ABI scePadReadHistory() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
|
||||
LOG_TRACE(Lib_Pad, "called");
|
||||
if (handle < 1) {
|
||||
LOG_TRACE(Lib_Pad, "handle: {}", handle);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
const auto* engine = controller->GetEngine();
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
auto& controller = *controllers[*controller_id];
|
||||
int connected_count = 0;
|
||||
bool connected = false;
|
||||
Input::State state;
|
||||
controller->ReadState(&state, &connected, &connected_count);
|
||||
ProcessStates(handle, pData, &state, 1, connected, connected_count);
|
||||
controller.ReadState(&state, &connected, &connected_count);
|
||||
ProcessStates(handle, pData, controller, &state, 1, connected, connected_count);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -450,13 +501,30 @@ int PS4_SYSV_ABI scePadReadStateExt() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadResetLightBar(s32 handle) {
|
||||
LOG_INFO(Lib_Pad, "(DUMMY) called");
|
||||
if (handle != 1) {
|
||||
LOG_DEBUG(Lib_Pad, "called, handle: {}", handle);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
int* rgb = Config::GetControllerCustomColor();
|
||||
controller->SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
s32 colour_index = UserManagement.GetUserByPlayerIndex(handle)->user_color - 1;
|
||||
Input::Colour colour{255, 0, 0};
|
||||
if (colour_index >= 0 && colour_index <= 3) {
|
||||
static constexpr Input::Colour colours[4]{
|
||||
{0, 0, 255}, // blue
|
||||
{255, 0, 0}, // red
|
||||
{0, 255, 0}, // green
|
||||
{255, 0, 255}, // pink
|
||||
};
|
||||
colour = colours[colour_index];
|
||||
} else {
|
||||
LOG_ERROR(Lib_Pad, "Invalid user colour value {} for controller {}, falling back to blue",
|
||||
colour_index, handle);
|
||||
}
|
||||
if (auto oc = GameControllers::GetControllerCustomColor(*controller_id)) {
|
||||
colour = *oc;
|
||||
}
|
||||
controllers[*controller_id]->SetLightBarRGB(colour.r, colour.g, colour.b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -473,14 +541,15 @@ int PS4_SYSV_ABI scePadResetLightBarAllByPortType() {
|
||||
int PS4_SYSV_ABI scePadResetOrientation(s32 handle) {
|
||||
LOG_INFO(Lib_Pad, "scePadResetOrientation called handle = {}", handle);
|
||||
|
||||
if (handle != 1) {
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
Libraries::Pad::OrbisFQuaternion defaultOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
controller->SetLastOrientation(defaultOrientation);
|
||||
controller->SetLastUpdate(std::chrono::steady_clock::now());
|
||||
controllers[*controller_id]->SetLastOrientation(defaultOrientation);
|
||||
controllers[*controller_id]->SetLastUpdate(std::chrono::steady_clock::now());
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
@ -526,7 +595,11 @@ int PS4_SYSV_ABI scePadSetForceIntercepted() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam) {
|
||||
if (Config::GetOverrideControllerColor()) {
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (GameControllers::GetControllerCustomColor(*controller_id)) {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
if (pParam != nullptr) {
|
||||
@ -538,8 +611,8 @@ int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pPar
|
||||
return ORBIS_PAD_ERROR_INVALID_LIGHTBAR_SETTING;
|
||||
}
|
||||
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
controller->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
@ -555,8 +628,14 @@ int PS4_SYSV_ABI scePadSetLightBarBlinking() {
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker() {
|
||||
LOG_ERROR(Lib_Pad, "(STUBBED) called");
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam) {
|
||||
LOG_INFO(Lib_Pad, "called, r: {} g: {} b: {}", pParam->r, pParam->g, pParam->b);
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
controllers[*controller_id]->SetLightBarRGB(pParam->r, pParam->g, pParam->b);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -603,11 +682,15 @@ int PS4_SYSV_ABI scePadSetUserColor() {
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI scePadSetVibration(s32 handle, const OrbisPadVibrationParam* pParam) {
|
||||
auto controller_id = GameControllers::GetControllerIndexFromControllerID(handle);
|
||||
if (!controller_id) {
|
||||
return ORBIS_PAD_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (pParam != nullptr) {
|
||||
LOG_DEBUG(Lib_Pad, "scePadSetVibration called handle = {} data = {} , {}", handle,
|
||||
pParam->smallMotor, pParam->largeMotor);
|
||||
auto* controller = Common::Singleton<GameController>::Instance();
|
||||
controller->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
||||
auto& controllers = *Common::Singleton<GameControllers>::Instance();
|
||||
controllers[*controller_id]->SetVibration(pParam->smallMotor, pParam->largeMotor);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
return ORBIS_PAD_ERROR_INVALID_ARG;
|
||||
|
||||
@ -253,6 +253,18 @@ struct OrbisPadVibrationParam {
|
||||
u8 smallMotor;
|
||||
};
|
||||
|
||||
struct OrbisPadInfo {
|
||||
u32 unk1;
|
||||
u32 unk2;
|
||||
u32 pad_handle;
|
||||
u32 unk3;
|
||||
u32 unk4;
|
||||
u32 unk5;
|
||||
u32 colour;
|
||||
u32 unk6;
|
||||
u32 unk[30];
|
||||
};
|
||||
|
||||
int PS4_SYSV_ABI scePadClose(s32 handle);
|
||||
int PS4_SYSV_ABI scePadConnectPort();
|
||||
int PS4_SYSV_ABI scePadDeviceClassGetExtendedInformation(
|
||||
@ -280,7 +292,7 @@ int PS4_SYSV_ABI scePadGetFeatureReport();
|
||||
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 scePadGetInfo(OrbisPadInfo* data);
|
||||
int PS4_SYSV_ABI scePadGetInfoByPortType();
|
||||
int PS4_SYSV_ABI scePadGetLicenseControllerInformation();
|
||||
int PS4_SYSV_ABI scePadGetMotionSensorPosition();
|
||||
@ -324,7 +336,7 @@ int PS4_SYSV_ABI scePadSetForceIntercepted();
|
||||
int PS4_SYSV_ABI scePadSetLightBar(s32 handle, const OrbisPadLightBarParam* pParam);
|
||||
int PS4_SYSV_ABI scePadSetLightBarBaseBrightness();
|
||||
int PS4_SYSV_ABI scePadSetLightBarBlinking();
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker();
|
||||
int PS4_SYSV_ABI scePadSetLightBarForTracker(s32 handle, const OrbisPadLightBarParam* pParam);
|
||||
int PS4_SYSV_ABI scePadSetLoginUserNumber();
|
||||
int PS4_SYSV_ABI scePadSetMotionSensorState(s32 handle, bool bEnable);
|
||||
int PS4_SYSV_ABI scePadSetProcessFocus();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <iostream>
|
||||
@ -6,7 +6,6 @@
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
@ -49,12 +48,13 @@ namespace Libraries::SaveData {
|
||||
|
||||
fs::path SaveInstance::MakeTitleSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial) {
|
||||
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial;
|
||||
return EmulatorSettings.GetHomeDir() / std::to_string(user_id) / "savedata" / game_serial;
|
||||
}
|
||||
|
||||
fs::path SaveInstance::MakeDirSavePath(Libraries::UserService::OrbisUserServiceUserId user_id,
|
||||
std::string_view game_serial, std::string_view dir_name) {
|
||||
return Config::GetSaveDataPath() / std::to_string(user_id) / game_serial / dir_name;
|
||||
fs::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_id, std::string_view game_serial,
|
||||
std::string_view dir_name) {
|
||||
return EmulatorSettings.GetHomeDir() / std::to_string(user_id) / "savedata" / game_serial /
|
||||
dir_name;
|
||||
}
|
||||
|
||||
uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) {
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
@ -8,13 +9,13 @@
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/cstring.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/enum.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "core/file_sys/fs.h"
|
||||
#include "core/libraries/error_codes.h"
|
||||
@ -441,7 +442,8 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
|
||||
LOG_INFO(Lib_SaveData, "called with invalid block size");
|
||||
}
|
||||
|
||||
const auto root_save = Config::GetSaveDataPath();
|
||||
const auto root_save =
|
||||
EmulatorSettings.GetHomeDir() / std::to_string(mount_info->userId) / "savedata";
|
||||
fs::create_directories(root_save);
|
||||
const auto available = fs::space(root_save).available;
|
||||
|
||||
@ -489,7 +491,9 @@ static Error Umount(const OrbisSaveDataMountPoint* mountPoint, bool call_backup
|
||||
return Error::PARAMETER;
|
||||
}
|
||||
LOG_DEBUG(Lib_SaveData, "Umount mountPoint:{}", mountPoint->data.to_view());
|
||||
const std::string_view mount_point_str{mountPoint->data};
|
||||
|
||||
std::string mount_point_str = mountPoint->data.to_string();
|
||||
|
||||
for (auto& instance : g_mount_slots) {
|
||||
if (instance.has_value()) {
|
||||
const auto& slot_name = instance->GetMountPoint();
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "core/libraries/sysmodule/sysmodule_error.h"
|
||||
#include "core/libraries/sysmodule/sysmodule_internal.h"
|
||||
#include "core/libraries/sysmodule/sysmodule_table.h"
|
||||
#include "core/libraries/system_gesture/system_gesture.h"
|
||||
#include "core/linker.h"
|
||||
#include "emulator.h"
|
||||
|
||||
@ -223,7 +224,8 @@ s32 loadModuleInternal(s32 index, s32 argc, const void* argv, s32* res_out) {
|
||||
{"libSceAudiodec.sprx", nullptr},
|
||||
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
|
||||
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
|
||||
{"libSceFreeTypeOt.sprx", nullptr}});
|
||||
{"libSceFreeTypeOt.sprx", nullptr},
|
||||
{"libSceSystemGesture.sprx", &Libraries::SystemGesture::RegisterLib}});
|
||||
|
||||
// Iterate through the allowed array
|
||||
const auto it = std::ranges::find_if(
|
||||
|
||||
@ -1,13 +1,19 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/config.h"
|
||||
#include <queue>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <core/user_settings.h>
|
||||
#include <queue>
|
||||
#include "common/singleton.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/libs.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/libraries/system/userservice_error.h"
|
||||
#include "core/tls.h"
|
||||
#include "input/controller.h"
|
||||
|
||||
namespace Libraries::UserService {
|
||||
|
||||
@ -114,14 +120,15 @@ void AddUserServiceEvent(const OrbisUserServiceEvent e) {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetEvent(OrbisUserServiceEvent* event) {
|
||||
LOG_TRACE(Lib_UserService, "(DUMMY) called");
|
||||
// fake a loggin event
|
||||
static bool logged_in = false;
|
||||
LOG_TRACE(Lib_UserService, "called");
|
||||
|
||||
if (!logged_in) {
|
||||
logged_in = true;
|
||||
event->event = OrbisUserServiceEventType::Login;
|
||||
event->userId = 1;
|
||||
if (!user_service_event_queue.empty()) {
|
||||
OrbisUserServiceEvent& temp = user_service_event_queue.front();
|
||||
event->event = temp.event;
|
||||
event->userId = temp.userId;
|
||||
user_service_event_queue.pop();
|
||||
LOG_INFO(Lib_UserService, "Event processed by the game: {} {}", (u8)temp.event,
|
||||
temp.userId);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -504,8 +511,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetInitialUser(int* user_id) {
|
||||
LOG_ERROR(Lib_UserService, "user_id is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
// select first user (TODO add more)
|
||||
*user_id = 1;
|
||||
*user_id = UserManagement.GetDefaultUser().user_id;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -575,20 +581,29 @@ int PS4_SYSV_ABI sceUserServiceGetLoginFlag() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetLoginUserIdList(OrbisUserServiceLoginUserIdList* userIdList) {
|
||||
LOG_DEBUG(Lib_UserService, "called");
|
||||
if (userIdList == nullptr) {
|
||||
LOG_ERROR(Lib_UserService, "user_id is null");
|
||||
LOG_ERROR(Lib_UserService, "userIdList is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
// TODO only first user, do the others as well
|
||||
userIdList->user_id[0] = 1;
|
||||
userIdList->user_id[1] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[2] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[3] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
|
||||
// Initialize all slots to invalid (-1)
|
||||
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
|
||||
userIdList->user_id[i] = ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
}
|
||||
|
||||
auto& user_manager = UserManagement;
|
||||
|
||||
auto logged_in_users = user_manager.GetLoggedInUsers();
|
||||
|
||||
for (int i = 0; i < ORBIS_USER_SERVICE_MAX_LOGIN_USERS; i++) {
|
||||
s32 id =
|
||||
logged_in_users[i] ? logged_in_users[i]->user_id : ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
userIdList->user_id[i] = id;
|
||||
LOG_DEBUG(Lib_UserService, "Slot {}: User ID {} (port {})", i, id,
|
||||
logged_in_users[i] ? logged_in_users[i]->player_index : -1);
|
||||
}
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceUserServiceGetMicLevel() {
|
||||
LOG_ERROR(Lib_UserService, "(STUBBED) called");
|
||||
return ORBIS_OK;
|
||||
@ -1056,7 +1071,7 @@ s32 PS4_SYSV_ABI sceUserServiceGetUserColor(int user_id, OrbisUserServiceUserCol
|
||||
LOG_ERROR(Lib_UserService, "color is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
*color = OrbisUserServiceUserColor::Blue;
|
||||
*color = (OrbisUserServiceUserColor)UserManagement.GetUserByID(user_id)->user_color;
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
@ -1076,12 +1091,18 @@ int PS4_SYSV_ABI sceUserServiceGetUserGroupNum() {
|
||||
}
|
||||
|
||||
s32 PS4_SYSV_ABI sceUserServiceGetUserName(int user_id, char* user_name, std::size_t size) {
|
||||
LOG_DEBUG(Lib_UserService, "called user_id = {} ,size = {} ", user_id, size);
|
||||
LOG_DEBUG(Lib_UserService, "called user_id = {}, size = {} ", user_id, size);
|
||||
if (user_name == nullptr) {
|
||||
LOG_ERROR(Lib_UserService, "user_name is null");
|
||||
return ORBIS_USER_SERVICE_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
std::string name = Config::getUserName();
|
||||
std::string name = "shadPS4";
|
||||
auto const* u = UserManagement.GetUserByID(user_id);
|
||||
if (u != nullptr) {
|
||||
name = u->user_name;
|
||||
} else {
|
||||
LOG_ERROR(Lib_UserService, "No user found");
|
||||
}
|
||||
if (size < name.length()) {
|
||||
LOG_ERROR(Lib_UserService, "buffer is too short");
|
||||
return ORBIS_USER_SERVICE_ERROR_BUFFER_TOO_SHORT;
|
||||
|
||||
@ -203,36 +203,23 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp
|
||||
LOG_ERROR(Lib_Vdec2, "No picture info available");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
if (gPictureInfos.empty()) {
|
||||
LOG_ERROR(Lib_Vdec2, "No picture info available");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
// If the game uses the older Videodec2 structs, we need to accomodate that.
|
||||
if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) {
|
||||
if (gLegacyPictureInfos.empty()) {
|
||||
LOG_ERROR(Lib_Vdec2, "No picture info available");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
if (p1stPictureInfoOut) {
|
||||
OrbisVideodec2LegacyAvcPictureInfo* picInfo =
|
||||
static_cast<OrbisVideodec2LegacyAvcPictureInfo*>(p1stPictureInfoOut);
|
||||
if (picInfo->thisSize != sizeof(OrbisVideodec2LegacyAvcPictureInfo)) {
|
||||
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
|
||||
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
|
||||
}
|
||||
*picInfo = gLegacyPictureInfos.back();
|
||||
}
|
||||
} else {
|
||||
if (gPictureInfos.empty()) {
|
||||
LOG_ERROR(Lib_Vdec2, "No picture info available");
|
||||
return ORBIS_OK;
|
||||
}
|
||||
if (p1stPictureInfoOut) {
|
||||
OrbisVideodec2AvcPictureInfo* picInfo =
|
||||
static_cast<OrbisVideodec2AvcPictureInfo*>(p1stPictureInfoOut);
|
||||
if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) {
|
||||
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
|
||||
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
|
||||
}
|
||||
*picInfo = gPictureInfos.back();
|
||||
if (p1stPictureInfoOut) {
|
||||
// Copy enough data to check thisSize.
|
||||
u64 picture_size = 0;
|
||||
memcpy(&picture_size, p1stPictureInfoOut, sizeof(u64));
|
||||
if ((picture_size | 0x10) != sizeof(OrbisVideodec2AvcPictureInfo)) {
|
||||
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
|
||||
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
|
||||
}
|
||||
// Copy the requested picture data to the output.
|
||||
memcpy(p1stPictureInfoOut, &gPictureInfos.back(), picture_size);
|
||||
// Correct the outputted picture struct size.
|
||||
memcpy(p1stPictureInfoOut, &picture_size, sizeof(u64));
|
||||
}
|
||||
|
||||
if (outputInfo->pictureCount > 1) {
|
||||
|
||||
@ -74,57 +74,4 @@ struct OrbisVideodec2AvcPictureInfo {
|
||||
};
|
||||
static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78);
|
||||
|
||||
// An older version of the OrbisVideodec2AvcPictureInfo struct
|
||||
// Keeping this is needed for compatiblity with older games.
|
||||
struct OrbisVideodec2LegacyAvcPictureInfo {
|
||||
u64 thisSize;
|
||||
|
||||
bool isValid;
|
||||
|
||||
u64 ptsData;
|
||||
u64 dtsData;
|
||||
u64 attachedData;
|
||||
|
||||
u8 idrPictureflag;
|
||||
|
||||
u8 profile_idc;
|
||||
u8 level_idc;
|
||||
u32 pic_width_in_mbs_minus1;
|
||||
u32 pic_height_in_map_units_minus1;
|
||||
u8 frame_mbs_only_flag;
|
||||
|
||||
u8 frame_cropping_flag;
|
||||
u32 frameCropLeftOffset;
|
||||
u32 frameCropRightOffset;
|
||||
u32 frameCropTopOffset;
|
||||
u32 frameCropBottomOffset;
|
||||
|
||||
u8 aspect_ratio_info_present_flag;
|
||||
u8 aspect_ratio_idc;
|
||||
u16 sar_width;
|
||||
u16 sar_height;
|
||||
|
||||
u8 video_signal_type_present_flag;
|
||||
u8 video_format;
|
||||
u8 video_full_range_flag;
|
||||
u8 colour_description_present_flag;
|
||||
u8 colour_primaries;
|
||||
u8 transfer_characteristics;
|
||||
u8 matrix_coefficients;
|
||||
|
||||
u8 timing_info_present_flag;
|
||||
u32 num_units_in_tick;
|
||||
u32 time_scale;
|
||||
u8 fixed_frame_rate_flag;
|
||||
|
||||
u8 bitstream_restriction_flag;
|
||||
u8 max_dec_frame_buffering;
|
||||
|
||||
u8 pic_struct_present_flag;
|
||||
u8 pic_struct;
|
||||
u8 field_pic_flag;
|
||||
u8 bottom_field_flag;
|
||||
};
|
||||
static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68);
|
||||
|
||||
} // namespace Libraries::Videodec2
|
||||
@ -12,7 +12,6 @@
|
||||
namespace Libraries::Videodec2 {
|
||||
|
||||
std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
|
||||
std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
|
||||
|
||||
static inline void CopyNV12Data(u8* dst, const AVFrame& src) {
|
||||
if (src.width == src.linesize[0]) {
|
||||
@ -132,46 +131,27 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
|
||||
outputInfo.isErrorFrame = false;
|
||||
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
|
||||
|
||||
// For proper compatibility with older games, check the inputted OutputInfo struct size.
|
||||
// Only set framePitchInBytes if the game uses the newer struct version.
|
||||
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
|
||||
// framePitchInBytes only exists in the newer struct.
|
||||
outputInfo.framePitchInBytes = frame->width;
|
||||
if (outputInfo.isValid) {
|
||||
OrbisVideodec2AvcPictureInfo pictureInfo = {};
|
||||
outputInfo.framePitchInBytes = frame->linesize[0];
|
||||
}
|
||||
|
||||
pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo);
|
||||
pictureInfo.isValid = true;
|
||||
if (outputInfo.isValid) {
|
||||
OrbisVideodec2AvcPictureInfo pictureInfo = {};
|
||||
|
||||
pictureInfo.ptsData = inputData.ptsData;
|
||||
pictureInfo.dtsData = inputData.dtsData;
|
||||
pictureInfo.attachedData = inputData.attachedData;
|
||||
pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo);
|
||||
pictureInfo.isValid = true;
|
||||
|
||||
pictureInfo.frameCropLeftOffset = frame->crop_left;
|
||||
pictureInfo.frameCropRightOffset = frame->crop_right;
|
||||
pictureInfo.frameCropTopOffset = frame->crop_top;
|
||||
pictureInfo.frameCropBottomOffset = frame->crop_bottom;
|
||||
pictureInfo.ptsData = inputData.ptsData;
|
||||
pictureInfo.dtsData = inputData.dtsData;
|
||||
pictureInfo.attachedData = inputData.attachedData;
|
||||
|
||||
gPictureInfos.push_back(pictureInfo);
|
||||
}
|
||||
} else {
|
||||
if (outputInfo.isValid) {
|
||||
// If the game uses the older struct versions, we need to use it too.
|
||||
OrbisVideodec2LegacyAvcPictureInfo pictureInfo = {};
|
||||
pictureInfo.frameCropLeftOffset = frame->crop_left;
|
||||
pictureInfo.frameCropRightOffset = frame->crop_right;
|
||||
pictureInfo.frameCropTopOffset = frame->crop_top;
|
||||
pictureInfo.frameCropBottomOffset = frame->crop_bottom;
|
||||
|
||||
pictureInfo.thisSize = sizeof(OrbisVideodec2LegacyAvcPictureInfo);
|
||||
pictureInfo.isValid = true;
|
||||
|
||||
pictureInfo.ptsData = inputData.ptsData;
|
||||
pictureInfo.dtsData = inputData.dtsData;
|
||||
pictureInfo.attachedData = inputData.attachedData;
|
||||
|
||||
pictureInfo.frameCropLeftOffset = frame->crop_left;
|
||||
pictureInfo.frameCropRightOffset = frame->crop_right;
|
||||
pictureInfo.frameCropTopOffset = frame->crop_top;
|
||||
pictureInfo.frameCropBottomOffset = frame->crop_bottom;
|
||||
|
||||
gLegacyPictureInfos.push_back(pictureInfo);
|
||||
}
|
||||
gPictureInfos.push_back(pictureInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@ extern "C" {
|
||||
namespace Libraries::Videodec2 {
|
||||
|
||||
extern std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
|
||||
extern std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
|
||||
|
||||
class VdecDecoder {
|
||||
public:
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
#include "common/singleton.h"
|
||||
#include "core/debugger.h"
|
||||
#include "core/devtools/widget/module_list.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/emulator_state.h"
|
||||
#include "core/file_format/psf.h"
|
||||
#include "core/file_format/trp.h"
|
||||
@ -38,6 +39,7 @@
|
||||
#include "core/libraries/save_data/save_backup.h"
|
||||
#include "core/linker.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "emulator.h"
|
||||
#include "video_core/cache_storage.h"
|
||||
#include "video_core/renderdoc.h"
|
||||
@ -50,6 +52,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include <core/file_format/npbind.h>
|
||||
|
||||
Frontend::WindowSDL* g_window = nullptr;
|
||||
|
||||
@ -106,7 +109,8 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
} else {
|
||||
game_folder = file.parent_path();
|
||||
if (const auto game_folder_name = game_folder.filename().string();
|
||||
game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) {
|
||||
game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch") ||
|
||||
game_folder_name.ends_with("-mods")) {
|
||||
// If an executable was launched from a separate update directory,
|
||||
// use the base game directory as the game folder.
|
||||
const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-'));
|
||||
@ -196,14 +200,20 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
|
||||
game_info.game_folder = game_folder;
|
||||
std::filesystem::path npbindPath = game_folder / "sce_sys/npbind.dat";
|
||||
NPBindFile npbind;
|
||||
if (!npbind.Load(npbindPath.string())) {
|
||||
LOG_WARNING(Common_Filesystem, "Failed to load npbind.dat file");
|
||||
} else {
|
||||
auto npCommIds = npbind.GetNpCommIds();
|
||||
if (npCommIds.empty()) {
|
||||
LOG_WARNING(Common_Filesystem, "No NPComm IDs found in npbind.dat");
|
||||
} else {
|
||||
game_info.npCommIds = std::move(npCommIds);
|
||||
}
|
||||
}
|
||||
|
||||
EmulatorSettings.Load(id);
|
||||
if (std::filesystem::exists(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) /
|
||||
(id + ".json"))) {
|
||||
EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(true);
|
||||
} else {
|
||||
EmulatorState::GetInstance()->SetGameSpecifigConfigUsed(false);
|
||||
}
|
||||
|
||||
// Initialize logging as soon as possible
|
||||
if (!id.empty() && EmulatorSettings.IsSeparateLoggingEnabled()) {
|
||||
@ -224,9 +234,8 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
LOG_INFO(Loader, "Description {}", Common::g_scm_desc);
|
||||
LOG_INFO(Loader, "Remote {}", Common::g_scm_remote_url);
|
||||
|
||||
const bool has_game_config = std::filesystem::exists(
|
||||
Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".json"));
|
||||
LOG_INFO(Config, "Game-specific config exists: {}", has_game_config);
|
||||
LOG_INFO(Config, "Game-specific config used: {}",
|
||||
EmulatorState::GetInstance()->IsGameSpecifigConfigUsed());
|
||||
|
||||
LOG_INFO(Config, "General LogType: {}", EmulatorSettings.GetLogType());
|
||||
LOG_INFO(Config, "General isIdenticalLogGrouped: {}", EmulatorSettings.IsIdenticalLogGrouped());
|
||||
@ -284,12 +293,19 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path mods_folder = game_folder;
|
||||
mods_folder += "-mods";
|
||||
|
||||
if (std::filesystem::exists(mods_folder) && !std::filesystem::is_empty(mods_folder)) {
|
||||
LOG_INFO(Loader, "Files found in game mods folder");
|
||||
}
|
||||
|
||||
// Create stdin/stdout/stderr
|
||||
Common::Singleton<FileSys::HandleTable>::Instance()->CreateStdHandles();
|
||||
|
||||
// Initialize components
|
||||
memory = Core::Memory::Instance();
|
||||
controller = Common::Singleton<Input::GameController>::Instance();
|
||||
controllers = Common::Singleton<Input::GameControllers>::Instance();
|
||||
linker = Common::Singleton<Core::Linker>::Instance();
|
||||
|
||||
// Load renderdoc module
|
||||
@ -298,15 +314,30 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
// Initialize patcher and trophies
|
||||
if (!id.empty()) {
|
||||
MemoryPatcher::g_game_serial = id;
|
||||
Libraries::Np::NpTrophy::game_serial = id;
|
||||
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / id / "TrophyFiles";
|
||||
if (!std::filesystem::exists(trophyDir)) {
|
||||
TRP trp;
|
||||
if (!trp.Extract(game_folder, id)) {
|
||||
LOG_ERROR(Loader, "Couldn't extract trophies");
|
||||
int index = 0;
|
||||
for (std::string npCommId : game_info.npCommIds) {
|
||||
const auto trophyDir =
|
||||
Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "trophy" / npCommId;
|
||||
if (!std::filesystem::exists(trophyDir)) {
|
||||
TRP trp;
|
||||
if (!trp.Extract(game_folder, index, npCommId, trophyDir)) {
|
||||
LOG_ERROR(Loader, "Couldn't extract trophies");
|
||||
}
|
||||
}
|
||||
for (User user : UserSettings.GetUserManager().GetValidUsers()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,7 +362,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
|
||||
}
|
||||
}
|
||||
window = std::make_unique<Frontend::WindowSDL>(EmulatorSettings.GetWindowWidth(),
|
||||
EmulatorSettings.GetWindowHeight(), controller,
|
||||
EmulatorSettings.GetWindowHeight(), controllers,
|
||||
window_title);
|
||||
|
||||
g_window = window.get();
|
||||
@ -512,7 +543,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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -43,7 +43,7 @@ private:
|
||||
void LoadSystemModules(const std::string& game_serial);
|
||||
|
||||
Core::MemoryManager* memory;
|
||||
Input::GameController* controller;
|
||||
Input::GameControllers* controllers;
|
||||
Core::Linker* linker;
|
||||
std::unique_ptr<Frontend::WindowSDL> window;
|
||||
std::chrono::steady_clock::time_point start_time;
|
||||
|
||||
58
src/imgui/imgui_translations.cpp
Normal file
58
src/imgui/imgui_translations.cpp
Normal 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
|
||||
136
src/imgui/imgui_translations.h
Normal file
136
src/imgui/imgui_translations.h
Normal 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
|
||||
@ -737,9 +737,8 @@ static void UpdateGamepads() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
SdlData* bd = GetBackendData();
|
||||
|
||||
auto controller = Common::Singleton<Input::GameController>::Instance();
|
||||
auto engine = controller->GetEngine();
|
||||
SDL_Gamepad* SDLGamepad = engine->m_gamepad;
|
||||
auto& controllers = *Common::Singleton<Input::GameControllers>::Instance();
|
||||
SDL_Gamepad* SDLGamepad = controllers[0]->m_sdl_gamepad;
|
||||
// Update list of gamepads to use
|
||||
if (bd->want_update_gamepads_list && bd->gamepad_mode != ImGui_ImplSDL3_GamepadMode_Manual) {
|
||||
if (SDLGamepad) {
|
||||
|
||||
@ -1219,6 +1219,10 @@ void ImGuiImplVulkanDestroyDeviceObjects() {
|
||||
v.device.destroyDescriptorSetLayout(bd->descriptor_set_layout, v.allocator);
|
||||
bd->descriptor_set_layout = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bd->descriptor_pool) {
|
||||
v.device.destroyDescriptorPool(bd->descriptor_pool, v.allocator);
|
||||
bd->descriptor_pool = VK_NULL_HANDLE;
|
||||
}
|
||||
if (bd->pipeline_layout) {
|
||||
v.device.destroyPipelineLayout(bd->pipeline_layout, v.allocator);
|
||||
bd->pipeline_layout = VK_NULL_HANDLE;
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024-2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
#include <SDL3/SDL.h>
|
||||
#include "common/config.h"
|
||||
#include <common/elf_info.h>
|
||||
#include <common/singleton.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "controller.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "input/controller.h"
|
||||
|
||||
static std::string SelectedGamepad = "";
|
||||
|
||||
namespace Input {
|
||||
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
@ -22,7 +27,15 @@ void State::OnButton(OrbisPadButtonDataOffset button, bool isPressed) {
|
||||
}
|
||||
}
|
||||
|
||||
void State::OnAxis(Axis axis, int value) {
|
||||
void State::OnAxis(Axis axis, int value, bool smooth) {
|
||||
auto const i = std::to_underlying(axis);
|
||||
// forcibly finish the previous smoothing task by jumping to the end
|
||||
axes[i] = axis_smoothing_end_values[i];
|
||||
|
||||
axis_smoothing_start_times[i] = time;
|
||||
axis_smoothing_start_values[i] = axes[i];
|
||||
axis_smoothing_end_values[i] = value;
|
||||
axis_smoothing_flags[i] = smooth;
|
||||
const auto toggle = [&](const auto button) {
|
||||
if (value > 0) {
|
||||
buttonsState |= button;
|
||||
@ -40,7 +53,6 @@ void State::OnAxis(Axis axis, int value) {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
axes[static_cast<int>(axis)] = value;
|
||||
}
|
||||
|
||||
void State::OnTouchpad(int touchIndex, bool isDown, float x, float y) {
|
||||
@ -61,6 +73,22 @@ void State::OnAccel(const float accel[3]) {
|
||||
acceleration.z = accel[2];
|
||||
}
|
||||
|
||||
void State::UpdateAxisSmoothing() {
|
||||
for (int i = 0; i < std::to_underlying(Axis::AxisMax); i++) {
|
||||
// if it's not to be smoothed or close enough, just jump to the end
|
||||
if (!axis_smoothing_flags[i] || std::abs(axes[i] - axis_smoothing_end_values[i]) < 16) {
|
||||
if (axes[i] != axis_smoothing_end_values[i]) {
|
||||
axes[i] = axis_smoothing_end_values[i];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto now = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
f32 t =
|
||||
std::clamp((now - axis_smoothing_start_times[i]) / f32{axis_smoothing_time}, 0.f, 1.f);
|
||||
axes[i] = s32(axis_smoothing_start_values[i] * (1 - t) + axis_smoothing_end_values[i] * t);
|
||||
}
|
||||
}
|
||||
|
||||
GameController::GameController() : m_states_queue(64) {}
|
||||
|
||||
void GameController::ReadState(State* state, bool* isConnected, int* connectedCount) {
|
||||
@ -88,31 +116,86 @@ int GameController::ReadStates(State* states, int states_num, bool* isConnected,
|
||||
return ret_num;
|
||||
}
|
||||
|
||||
void GameController::Button(int id, OrbisPadButtonDataOffset button, bool is_pressed) {
|
||||
void GameController::Button(OrbisPadButtonDataOffset button, bool is_pressed) {
|
||||
m_state.OnButton(button, is_pressed);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::Axis(int id, Input::Axis axis, int value) {
|
||||
m_state.OnAxis(axis, value);
|
||||
void GameController::Axis(Input::Axis axis, int value, bool smooth) {
|
||||
m_state.OnAxis(axis, value, smooth);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::Gyro(int id, const float gyro[3]) {
|
||||
m_state.OnGyro(gyro);
|
||||
void GameController::Gyro(int id) {
|
||||
m_state.OnGyro(gyro_buf);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::Acceleration(int id, const float acceleration[3]) {
|
||||
m_state.OnAccel(acceleration);
|
||||
void GameController::Acceleration(int id) {
|
||||
m_state.OnAccel(accel_buf);
|
||||
PushState();
|
||||
}
|
||||
|
||||
void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||
float deltaTime,
|
||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||
Libraries::Pad::OrbisFQuaternion& orientation) {
|
||||
void GameController::UpdateGyro(const float gyro[3]) {
|
||||
std::scoped_lock l(m_states_queue_mutex);
|
||||
std::memcpy(gyro_buf, gyro, sizeof(gyro_buf));
|
||||
}
|
||||
|
||||
void GameController::UpdateAcceleration(const float acceleration[3]) {
|
||||
std::scoped_lock l(m_states_queue_mutex);
|
||||
std::memcpy(accel_buf, acceleration, sizeof(accel_buf));
|
||||
}
|
||||
|
||||
void GameController::UpdateAxisSmoothing() {
|
||||
m_state.UpdateAxisSmoothing();
|
||||
}
|
||||
|
||||
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||
colour = {r, g, b};
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
SDL_SetGamepadLED(m_sdl_gamepad, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
Colour GameController::GetLightBarRGB() {
|
||||
return colour;
|
||||
}
|
||||
|
||||
void GameController::PollLightColour() {
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
SDL_SetGamepadLED(m_sdl_gamepad, colour.r, colour.g, colour.b);
|
||||
}
|
||||
}
|
||||
|
||||
bool GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (m_sdl_gamepad != nullptr) {
|
||||
return SDL_RumbleGamepad(m_sdl_gamepad, (smallMotor / 255.0f) * 0xFFFF,
|
||||
(largeMotor / 255.0f) * 0xFFFF, -1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
|
||||
if (touchIndex < 2) {
|
||||
m_state.OnTouchpad(touchIndex, touchDown, x, y);
|
||||
PushState();
|
||||
}
|
||||
}
|
||||
|
||||
std::array<std::optional<Colour>, 4> GameControllers::controller_override_colors{
|
||||
std::nullopt, std::nullopt, std::nullopt, std::nullopt};
|
||||
|
||||
void GameControllers::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||
float deltaTime,
|
||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||
Libraries::Pad::OrbisFQuaternion& orientation) {
|
||||
// avoid wildly off values coming from elapsed time between two samples
|
||||
// being too high, such as on the first time the controller is polled
|
||||
if (deltaTime > 1.0f) {
|
||||
orientation = lastOrientation;
|
||||
return;
|
||||
}
|
||||
Libraries::Pad::OrbisFQuaternion q = lastOrientation;
|
||||
Libraries::Pad::OrbisFQuaternion ω = {angularVelocity.x, angularVelocity.y, angularVelocity.z,
|
||||
0.0f};
|
||||
@ -143,27 +226,100 @@ void GameController::CalculateOrientation(Libraries::Pad::OrbisFVector3& acceler
|
||||
orientation.y, orientation.z, orientation.w);
|
||||
}
|
||||
|
||||
void GameController::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||
if (!m_engine) {
|
||||
return;
|
||||
}
|
||||
m_engine->SetLightBarRGB(r, g, b);
|
||||
}
|
||||
bool is_first_check = true;
|
||||
|
||||
void GameController::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (!m_engine) {
|
||||
return;
|
||||
}
|
||||
m_engine->SetVibration(smallMotor, largeMotor);
|
||||
}
|
||||
void GameControllers::TryOpenSDLControllers() {
|
||||
using namespace Libraries::UserService;
|
||||
int controller_count;
|
||||
s32 move_count = 0;
|
||||
SDL_JoystickID* new_joysticks = SDL_GetGamepads(&controller_count);
|
||||
LOG_INFO(Input, "{} controllers are currently connected", controller_count);
|
||||
|
||||
void GameController::SetTouchpadState(int touchIndex, bool touchDown, float x, float y) {
|
||||
if (touchIndex < 2) {
|
||||
m_state.OnTouchpad(touchIndex, touchDown, x, y);
|
||||
PushState();
|
||||
}
|
||||
}
|
||||
std::unordered_set<SDL_JoystickID> assigned_ids;
|
||||
std::array<bool, 4> slot_taken{false, false, false, false};
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
SDL_Gamepad* pad = controllers[i]->m_sdl_gamepad;
|
||||
if (pad) {
|
||||
SDL_JoystickID id = SDL_GetGamepadID(pad);
|
||||
bool still_connected = false;
|
||||
ControllerType type = ControllerType::Standard;
|
||||
for (int j = 0; j < controller_count; j++) {
|
||||
if (new_joysticks[j] == id) {
|
||||
still_connected = true;
|
||||
assigned_ids.insert(id);
|
||||
slot_taken[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!still_connected) {
|
||||
auto u = UserManagement.GetUserByID(controllers[i]->user_id);
|
||||
UserManagement.LogoutUser(u);
|
||||
SDL_CloseGamepad(pad);
|
||||
controllers[i]->m_sdl_gamepad = nullptr;
|
||||
controllers[i]->user_id = -1;
|
||||
slot_taken[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < controller_count; j++) {
|
||||
SDL_JoystickID id = new_joysticks[j];
|
||||
if (assigned_ids.contains(id))
|
||||
continue;
|
||||
|
||||
SDL_Gamepad* pad = SDL_OpenGamepad(id);
|
||||
if (!pad) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!slot_taken[i]) {
|
||||
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
|
||||
if (!u) {
|
||||
LOG_INFO(Input, "User {} not found", i + 1);
|
||||
continue; // for now, if you don't specify who Player N is in the config,
|
||||
// Player N won't be registered at all
|
||||
}
|
||||
auto* c = controllers[i];
|
||||
c->m_sdl_gamepad = pad;
|
||||
LOG_INFO(Input, "Gamepad registered for slot {}! Handle: {}", i,
|
||||
SDL_GetGamepadID(pad));
|
||||
c->user_id = u->user_id;
|
||||
slot_taken[i] = true;
|
||||
UserManagement.LoginUser(u, i + 1);
|
||||
if (EmulatorSettings.IsMotionControlsEnabled()) {
|
||||
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_GYRO, true)) {
|
||||
c->gyro_poll_rate =
|
||||
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_GYRO);
|
||||
LOG_INFO(Input, "Gyro initialized, poll rate: {}", c->gyro_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad {}",
|
||||
c->user_id);
|
||||
}
|
||||
if (SDL_SetGamepadSensorEnabled(c->m_sdl_gamepad, SDL_SENSOR_ACCEL, true)) {
|
||||
c->accel_poll_rate =
|
||||
SDL_GetGamepadSensorDataRate(c->m_sdl_gamepad, SDL_SENSOR_ACCEL);
|
||||
LOG_INFO(Input, "Accel initialized, poll rate: {}", c->accel_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad {}",
|
||||
c->user_id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is_first_check) [[unlikely]] {
|
||||
is_first_check = false;
|
||||
if (controller_count - move_count == 0) {
|
||||
auto u = UserManagement.GetUserByPlayerIndex(1);
|
||||
controllers[0]->user_id = u->user_id;
|
||||
UserManagement.LoginUser(u, 1);
|
||||
}
|
||||
}
|
||||
SDL_free(new_joysticks);
|
||||
}
|
||||
u8 GameController::GetTouchCount() {
|
||||
return m_touch_count;
|
||||
}
|
||||
@ -215,73 +371,37 @@ void GameController::SetLastUpdate(std::chrono::steady_clock::time_point lastUpd
|
||||
m_last_update = lastUpdate;
|
||||
}
|
||||
|
||||
void GameController::SetEngine(std::unique_ptr<Engine> engine) {
|
||||
m_engine = std::move(engine);
|
||||
if (m_engine) {
|
||||
m_engine->Init();
|
||||
}
|
||||
}
|
||||
|
||||
Engine* GameController::GetEngine() {
|
||||
return m_engine.get();
|
||||
}
|
||||
|
||||
void GameController::PushState() {
|
||||
std::lock_guard lg(m_states_queue_mutex);
|
||||
m_state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
m_states_queue.Push(m_state);
|
||||
}
|
||||
|
||||
u32 GameController::Poll() {
|
||||
if (m_connected) {
|
||||
PushState();
|
||||
}
|
||||
return 33;
|
||||
}
|
||||
|
||||
} // namespace Input
|
||||
|
||||
namespace GamepadSelect {
|
||||
|
||||
int GetDefaultGamepad(SDL_JoystickID* gamepadIDs, int gamepadCount) {
|
||||
char GUIDbuf[33];
|
||||
if (Config::getDefaultControllerID() != "") {
|
||||
for (int i = 0; i < gamepadCount; i++) {
|
||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
|
||||
std::string currentGUID = std::string(GUIDbuf);
|
||||
if (currentGUID == Config::getDefaultControllerID()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID) {
|
||||
char GUIDbuf[33];
|
||||
for (int i = 0; i < gamepadCount; i++) {
|
||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[i]), GUIDbuf, 33);
|
||||
std::string currentGUID = std::string(GUIDbuf);
|
||||
if (currentGUID == GUID) {
|
||||
u8 GameControllers::GetGamepadIndexFromJoystickId(SDL_JoystickID id) {
|
||||
auto g = SDL_GetGamepadFromID(id);
|
||||
ASSERT(g != nullptr);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (controllers[i]->m_sdl_gamepad == g) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// LOG_TRACE(Input, "Gamepad index: {}", index);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index) {
|
||||
char GUIDbuf[33];
|
||||
SDL_GUIDToString(SDL_GetGamepadGUIDForID(gamepadIDs[index]), GUIDbuf, 33);
|
||||
std::string GUID = std::string(GUIDbuf);
|
||||
return GUID;
|
||||
std::optional<u8> GameControllers::GetControllerIndexFromUserID(s32 user_id) {
|
||||
auto const u = UserManagement.GetUserByID(user_id);
|
||||
if (!u) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return u->player_index - 1;
|
||||
}
|
||||
|
||||
std::string GetSelectedGamepad() {
|
||||
return SelectedGamepad;
|
||||
std::optional<u8> GameControllers::GetControllerIndexFromControllerID(s32 controller_id) {
|
||||
if (controller_id < 1 || controller_id > 5) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return controller_id - 1;
|
||||
}
|
||||
|
||||
void SetSelectedGamepad(std::string GUID) {
|
||||
SelectedGamepad = GUID;
|
||||
}
|
||||
|
||||
} // namespace GamepadSelect
|
||||
} // namespace Input
|
||||
|
||||
@ -3,18 +3,25 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <SDL3/SDL_gamepad.h>
|
||||
|
||||
#include "SDL3/SDL_joystick.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
|
||||
struct SDL_Gamepad;
|
||||
|
||||
namespace Input {
|
||||
|
||||
enum class ControllerType {
|
||||
Standard,
|
||||
};
|
||||
|
||||
enum class Axis {
|
||||
LeftX = 0,
|
||||
LeftY = 1,
|
||||
@ -33,37 +40,41 @@ struct TouchpadEntry {
|
||||
u16 y{};
|
||||
};
|
||||
|
||||
class State {
|
||||
struct Colour {
|
||||
u8 r, g, b;
|
||||
};
|
||||
|
||||
struct State {
|
||||
private:
|
||||
template <typename T>
|
||||
using AxisArray = std::array<T, std::to_underlying(Axis::AxisMax)>;
|
||||
static constexpr AxisArray<s32> axis_defaults{128, 128, 128, 128, 0, 0};
|
||||
static constexpr u64 axis_smoothing_time{33000};
|
||||
AxisArray<bool> axis_smoothing_flags{true};
|
||||
AxisArray<u64> axis_smoothing_start_times{0};
|
||||
AxisArray<int> axis_smoothing_start_values{axis_defaults};
|
||||
AxisArray<int> axis_smoothing_end_values{axis_defaults};
|
||||
|
||||
public:
|
||||
void OnButton(Libraries::Pad::OrbisPadButtonDataOffset, bool);
|
||||
void OnAxis(Axis, int);
|
||||
void OnAxis(Axis, int, bool smooth = true);
|
||||
void OnTouchpad(int touchIndex, bool isDown, float x, float y);
|
||||
void OnGyro(const float[3]);
|
||||
void OnAccel(const float[3]);
|
||||
void UpdateAxisSmoothing();
|
||||
|
||||
Libraries::Pad::OrbisPadButtonDataOffset buttonsState{};
|
||||
u64 time = 0;
|
||||
int axes[static_cast<int>(Axis::AxisMax)] = {128, 128, 128, 128, 0, 0};
|
||||
AxisArray<s32> axes{axis_defaults};
|
||||
TouchpadEntry touchpad[2] = {{false, 0, 0}, {false, 0, 0}};
|
||||
Libraries::Pad::OrbisFVector3 acceleration = {0.0f, 0.0f, 0.0f};
|
||||
Libraries::Pad::OrbisFVector3 acceleration = {0.0f, -9.81f, 0.0f};
|
||||
Libraries::Pad::OrbisFVector3 angularVelocity = {0.0f, 0.0f, 0.0f};
|
||||
Libraries::Pad::OrbisFQuaternion orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
};
|
||||
|
||||
class Engine {
|
||||
public:
|
||||
virtual ~Engine() = default;
|
||||
virtual void Init() = 0;
|
||||
virtual void SetLightBarRGB(u8 r, u8 g, u8 b) = 0;
|
||||
virtual void SetVibration(u8 smallMotor, u8 largeMotor) = 0;
|
||||
virtual State ReadState() = 0;
|
||||
virtual float GetAccelPollRate() const = 0;
|
||||
virtual float GetGyroPollRate() const = 0;
|
||||
SDL_Gamepad* m_gamepad;
|
||||
};
|
||||
|
||||
inline int GetAxis(int min, int max, int value) {
|
||||
return std::clamp((255 * (value - min)) / (max - min), 0, 255);
|
||||
int v = (255 * (value - min)) / (max - min);
|
||||
return (v < 0 ? 0 : (v > 255 ? 255 : v));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@ -98,6 +109,8 @@ private:
|
||||
};
|
||||
|
||||
class GameController {
|
||||
friend class GameControllers;
|
||||
|
||||
public:
|
||||
GameController();
|
||||
virtual ~GameController() = default;
|
||||
@ -105,16 +118,18 @@ public:
|
||||
void ReadState(State* state, bool* isConnected, int* connectedCount);
|
||||
int ReadStates(State* states, int states_num, bool* isConnected, int* connectedCount);
|
||||
|
||||
void Button(int id, Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
|
||||
void Axis(int id, Input::Axis axis, int value);
|
||||
void Gyro(int id, const float gyro[3]);
|
||||
void Acceleration(int id, const float acceleration[3]);
|
||||
void Button(Libraries::Pad::OrbisPadButtonDataOffset button, bool isPressed);
|
||||
void Axis(Input::Axis axis, int value, bool smooth = true);
|
||||
void Gyro(int id);
|
||||
void Acceleration(int id);
|
||||
void UpdateGyro(const float gyro[3]);
|
||||
void UpdateAcceleration(const float acceleration[3]);
|
||||
void UpdateAxisSmoothing();
|
||||
void SetLightBarRGB(u8 r, u8 g, u8 b);
|
||||
void SetVibration(u8 smallMotor, u8 largeMotor);
|
||||
Colour GetLightBarRGB();
|
||||
void PollLightColour();
|
||||
bool SetVibration(u8 smallMotor, u8 largeMotor);
|
||||
void SetTouchpadState(int touchIndex, bool touchDown, float x, float y);
|
||||
void SetEngine(std::unique_ptr<Engine>);
|
||||
Engine* GetEngine();
|
||||
u32 Poll();
|
||||
|
||||
u8 GetTouchCount();
|
||||
void SetTouchCount(u8 touchCount);
|
||||
@ -129,11 +144,12 @@ public:
|
||||
Libraries::Pad::OrbisFQuaternion GetLastOrientation();
|
||||
std::chrono::steady_clock::time_point GetLastUpdate();
|
||||
void SetLastUpdate(std::chrono::steady_clock::time_point lastUpdate);
|
||||
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||
float deltaTime,
|
||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||
Libraries::Pad::OrbisFQuaternion& orientation);
|
||||
|
||||
float gyro_poll_rate;
|
||||
float accel_poll_rate;
|
||||
float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f};
|
||||
s32 user_id = Libraries::UserService::ORBIS_USER_SERVICE_USER_ID_INVALID;
|
||||
SDL_Gamepad* m_sdl_gamepad = nullptr;
|
||||
|
||||
private:
|
||||
void PushState();
|
||||
@ -146,22 +162,46 @@ private:
|
||||
bool m_was_secondary_reset = false;
|
||||
std::chrono::steady_clock::time_point m_last_update = {};
|
||||
Libraries::Pad::OrbisFQuaternion m_orientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
Colour colour;
|
||||
|
||||
State m_state;
|
||||
|
||||
std::mutex m_states_queue_mutex;
|
||||
RingBufferQueue<State> m_states_queue;
|
||||
};
|
||||
|
||||
std::unique_ptr<Engine> m_engine = nullptr;
|
||||
class GameControllers {
|
||||
std::array<GameController*, 5> controllers;
|
||||
|
||||
static std::array<std::optional<Colour>, 4> controller_override_colors;
|
||||
|
||||
public:
|
||||
GameControllers()
|
||||
: controllers({new GameController(), new GameController(), new GameController(),
|
||||
new GameController(), new GameController()}) {};
|
||||
virtual ~GameControllers() = default;
|
||||
GameController* operator[](const size_t& i) const {
|
||||
if (i > 4) {
|
||||
UNREACHABLE_MSG("Index {} is out of bounds for GameControllers!", i);
|
||||
}
|
||||
return controllers[i];
|
||||
}
|
||||
void TryOpenSDLControllers();
|
||||
u8 GetGamepadIndexFromJoystickId(SDL_JoystickID id);
|
||||
static std::optional<u8> GetControllerIndexFromUserID(s32 user_id);
|
||||
static std::optional<u8> GetControllerIndexFromControllerID(s32 controller_id);
|
||||
|
||||
static void CalculateOrientation(Libraries::Pad::OrbisFVector3& acceleration,
|
||||
Libraries::Pad::OrbisFVector3& angularVelocity,
|
||||
float deltaTime,
|
||||
Libraries::Pad::OrbisFQuaternion& lastOrientation,
|
||||
Libraries::Pad::OrbisFQuaternion& orientation);
|
||||
static void SetControllerCustomColor(s32 i, u8 r, u8 g, u8 b) {
|
||||
controller_override_colors[i] = {r, g, b};
|
||||
}
|
||||
static std::optional<Colour> GetControllerCustomColor(s32 i) {
|
||||
return controller_override_colors[i];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Input
|
||||
|
||||
namespace GamepadSelect {
|
||||
|
||||
int GetIndexfromGUID(SDL_JoystickID* gamepadIDs, int gamepadCount, std::string GUID);
|
||||
std::string GetGUIDString(SDL_JoystickID* gamepadIDs, int index);
|
||||
std::string GetSelectedGamepad();
|
||||
void SetSelectedGamepad(std::string GUID);
|
||||
|
||||
} // namespace GamepadSelect
|
||||
|
||||
@ -18,10 +18,10 @@
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_timer.h"
|
||||
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "common/io_file.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/singleton.h"
|
||||
#include "core/devtools/layer.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/emulator_state.h"
|
||||
@ -43,78 +43,185 @@ What structs are needed?
|
||||
InputBinding(key1, key2, key3)
|
||||
ControllerOutput(button, axis) - we only need a const array of these, and one of the attr-s is
|
||||
always 0 BindingConnection(inputBinding (member), controllerOutput (ref to the array element))
|
||||
|
||||
Things to always test before pushing like a dumbass:
|
||||
Button outputs
|
||||
Axis outputs
|
||||
Input hierarchy
|
||||
Multi key inputs
|
||||
Mouse to joystick
|
||||
Key toggle
|
||||
Joystick halfmode
|
||||
|
||||
Don't be an idiot and test only the changed part expecting everything else to not be broken
|
||||
*/
|
||||
|
||||
constexpr std::string_view GetDefaultGlobalConfig() {
|
||||
return R"(# Anything put here will be loaded for all games,
|
||||
# alongside the game's config or default.ini depending on your preference.
|
||||
)";
|
||||
}
|
||||
|
||||
constexpr std::string_view GetDefaultInputConfig() {
|
||||
return R"(#Feeling lost? Check out the Help section!
|
||||
|
||||
# Keyboard bindings
|
||||
|
||||
triangle = kp8
|
||||
circle = kp6
|
||||
cross = kp2
|
||||
square = kp4
|
||||
# Alternatives for users without a keypad
|
||||
triangle = c
|
||||
circle = b
|
||||
cross = n
|
||||
square = v
|
||||
|
||||
l1 = q
|
||||
r1 = u
|
||||
l2 = e
|
||||
r2 = o
|
||||
l3 = x
|
||||
r3 = m
|
||||
|
||||
options = enter
|
||||
touchpad_center = space
|
||||
|
||||
pad_up = up
|
||||
pad_down = down
|
||||
pad_left = left
|
||||
pad_right = right
|
||||
|
||||
axis_left_x_minus = a
|
||||
axis_left_x_plus = d
|
||||
axis_left_y_minus = w
|
||||
axis_left_y_plus = s
|
||||
|
||||
axis_right_x_minus = j
|
||||
axis_right_x_plus = l
|
||||
axis_right_y_minus = i
|
||||
axis_right_y_plus = k
|
||||
|
||||
# Controller bindings
|
||||
|
||||
triangle = triangle
|
||||
cross = cross
|
||||
square = square
|
||||
circle = circle
|
||||
|
||||
l1 = l1
|
||||
l2 = l2
|
||||
l3 = l3
|
||||
r1 = r1
|
||||
r2 = r2
|
||||
r3 = r3
|
||||
|
||||
options = options
|
||||
touchpad_center = back
|
||||
|
||||
pad_up = pad_up
|
||||
pad_down = pad_down
|
||||
pad_left = pad_left
|
||||
pad_right = pad_right
|
||||
|
||||
axis_left_x = axis_left_x
|
||||
axis_left_y = axis_left_y
|
||||
axis_right_x = axis_right_x
|
||||
axis_right_y = axis_right_y
|
||||
|
||||
# Range of deadzones: 1 (almost none) to 127 (max)
|
||||
analog_deadzone = leftjoystick, 2, 127
|
||||
analog_deadzone = rightjoystick, 2, 127
|
||||
|
||||
override_controller_color = false, 0, 0, 255
|
||||
)";
|
||||
}
|
||||
std::filesystem::path GetInputConfigFile(const std::string& game_id) {
|
||||
// Read configuration file of the game, and if it doesn't exist, generate it from default
|
||||
// If that doesn't exist either, generate that from getDefaultConfig() and try again
|
||||
// If even the folder is missing, we start with that.
|
||||
|
||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "input_config";
|
||||
const auto config_file = config_dir / (game_id + ".ini");
|
||||
const auto default_config_file = config_dir / "default.ini";
|
||||
|
||||
// Ensure the config directory exists
|
||||
if (!std::filesystem::exists(config_dir)) {
|
||||
std::filesystem::create_directories(config_dir);
|
||||
}
|
||||
|
||||
// Check if the default config exists
|
||||
if (!std::filesystem::exists(default_config_file)) {
|
||||
// If the default config is also missing, create it from getDefaultConfig()
|
||||
const auto default_config = GetDefaultInputConfig();
|
||||
std::ofstream default_config_stream(default_config_file);
|
||||
if (default_config_stream) {
|
||||
default_config_stream << default_config;
|
||||
}
|
||||
}
|
||||
|
||||
// if empty, we only need to execute the function up until this point
|
||||
if (game_id.empty()) {
|
||||
return default_config_file;
|
||||
}
|
||||
|
||||
// Create global config if it doesn't exist yet
|
||||
if (game_id == "global" && !std::filesystem::exists(config_file)) {
|
||||
if (!std::filesystem::exists(config_file)) {
|
||||
const auto global_config = GetDefaultGlobalConfig();
|
||||
std::ofstream global_config_stream(config_file);
|
||||
if (global_config_stream) {
|
||||
global_config_stream << global_config;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (game_id == "global") {
|
||||
std::map<std::string, std::string> default_bindings_to_add = {
|
||||
{"hotkey_renderdoc_capture", "f12"},
|
||||
{"hotkey_fullscreen", "f11"},
|
||||
{"hotkey_show_fps", "f10"},
|
||||
{"hotkey_pause", "f9"},
|
||||
{"hotkey_reload_inputs", "f8"},
|
||||
{"hotkey_toggle_mouse_to_joystick", "f7"},
|
||||
{"hotkey_toggle_mouse_to_gyro", "f6"},
|
||||
{"hotkey_add_virtual_user", "f5"},
|
||||
{"hotkey_remove_virtual_user", "f4"},
|
||||
{"hotkey_toggle_mouse_to_touchpad", "delete"},
|
||||
{"hotkey_quit", "lctrl, lshift, end"},
|
||||
{"hotkey_volume_up", "kpplus"},
|
||||
{"hotkey_volume_down", "kpminus"},
|
||||
};
|
||||
std::ifstream global_in(config_file);
|
||||
std::string line;
|
||||
while (std::getline(global_in, line)) {
|
||||
line.erase(std::remove_if(line.begin(), line.end(),
|
||||
[](unsigned char c) { return std::isspace(c); }),
|
||||
line.end());
|
||||
std::size_t equal_pos = line.find('=');
|
||||
if (equal_pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
std::string output_string = line.substr(0, equal_pos);
|
||||
default_bindings_to_add.erase(output_string);
|
||||
}
|
||||
global_in.close();
|
||||
std::ofstream global_out(config_file, std::ios::app);
|
||||
for (auto const& b : default_bindings_to_add) {
|
||||
global_out << b.first << " = " << b.second << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// If game-specific config doesn't exist, create it from the default config
|
||||
if (!std::filesystem::exists(config_file)) {
|
||||
std::filesystem::copy(default_config_file, config_file);
|
||||
}
|
||||
return config_file;
|
||||
}
|
||||
|
||||
bool leftjoystick_halfmode = false, rightjoystick_halfmode = false;
|
||||
std::pair<int, int> leftjoystick_deadzone, rightjoystick_deadzone, lefttrigger_deadzone,
|
||||
righttrigger_deadzone;
|
||||
std::array<std::pair<int, int>, 4> leftjoystick_deadzone, rightjoystick_deadzone,
|
||||
lefttrigger_deadzone, righttrigger_deadzone;
|
||||
|
||||
std::list<std::pair<InputEvent, bool>> pressed_keys;
|
||||
std::list<InputID> toggled_keys;
|
||||
static std::vector<BindingConnection> connections;
|
||||
|
||||
auto output_array = std::array{
|
||||
// Important: these have to be the first, or else they will update in the wrong order
|
||||
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(KEY_TOGGLE),
|
||||
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
|
||||
GameControllers ControllerOutput::controllers =
|
||||
*Common::Singleton<Input::GameControllers>::Instance();
|
||||
|
||||
// Button mappings
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
||||
|
||||
// Axis mappings
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
|
||||
|
||||
ControllerOutput(HOTKEY_FULLSCREEN),
|
||||
ControllerOutput(HOTKEY_PAUSE),
|
||||
ControllerOutput(HOTKEY_SIMPLE_FPS),
|
||||
ControllerOutput(HOTKEY_QUIT),
|
||||
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
|
||||
ControllerOutput(HOTKEY_RENDERDOC),
|
||||
ControllerOutput(HOTKEY_VOLUME_UP),
|
||||
ControllerOutput(HOTKEY_VOLUME_DOWN),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
std::array<ControllerAllOutputs, 9> output_arrays = {
|
||||
ControllerAllOutputs(0), ControllerAllOutputs(1), ControllerAllOutputs(2),
|
||||
ControllerAllOutputs(3), ControllerAllOutputs(4), ControllerAllOutputs(5),
|
||||
ControllerAllOutputs(6), ControllerAllOutputs(7), ControllerAllOutputs(8),
|
||||
};
|
||||
|
||||
void ControllerOutput::LinkJoystickAxes() {
|
||||
@ -158,6 +265,8 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
||||
return OPBDO::TouchPad;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||
return OPBDO::L1;
|
||||
case SDL_GAMEPAD_BUTTON_MISC1: // Move
|
||||
return OPBDO::L1;
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return OPBDO::R1;
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_STICK:
|
||||
@ -223,10 +332,19 @@ InputBinding GetBindingFromString(std::string& line) {
|
||||
return InputBinding(keys[0], keys[1], keys[2]);
|
||||
}
|
||||
|
||||
std::optional<int> parseInt(const std::string& s) {
|
||||
try {
|
||||
return std::stoi(s);
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
void ParseInputConfig(const std::string game_id = "") {
|
||||
std::string game_id_or_default = Config::GetUseUnifiedInputConfig() ? "default" : game_id;
|
||||
const auto config_file = Config::GetInputConfigFile(game_id_or_default);
|
||||
const auto global_config_file = Config::GetInputConfigFile("global");
|
||||
std::string game_id_or_default =
|
||||
EmulatorSettings.IsUseUnifiedInputConfig() ? "default" : game_id;
|
||||
const auto config_file = GetInputConfigFile(game_id_or_default);
|
||||
const auto global_config_file = GetInputConfigFile("global");
|
||||
|
||||
// we reset these here so in case the user fucks up or doesn't include some of these,
|
||||
// we can fall back to default
|
||||
@ -235,13 +353,14 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
float mouse_speed = 1;
|
||||
float mouse_speed_offset = 0.125;
|
||||
|
||||
leftjoystick_deadzone = {1, 127};
|
||||
rightjoystick_deadzone = {1, 127};
|
||||
lefttrigger_deadzone = {1, 127};
|
||||
righttrigger_deadzone = {1, 127};
|
||||
// me when I'm in a type deduction tournament and my opponent is clang
|
||||
constexpr std::array<std::pair<int, int>, 4> default_deadzone = {
|
||||
std::pair{1, 127}, {1, 127}, {1, 127}, {1, 127}};
|
||||
|
||||
Config::SetOverrideControllerColor(false);
|
||||
Config::SetControllerCustomColor(0, 0, 255);
|
||||
leftjoystick_deadzone = default_deadzone;
|
||||
rightjoystick_deadzone = default_deadzone;
|
||||
lefttrigger_deadzone = default_deadzone;
|
||||
righttrigger_deadzone = default_deadzone;
|
||||
|
||||
int lineCount = 0;
|
||||
|
||||
@ -278,21 +397,37 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
|
||||
std::string output_string = line.substr(0, equal_pos);
|
||||
std::string input_string = line.substr(equal_pos + 1);
|
||||
// Remove trailing semicolon from input_string
|
||||
if (!input_string.empty() && input_string[input_string.length() - 1] == ';' &&
|
||||
input_string != ";") {
|
||||
line = line.substr(0, line.length() - 1);
|
||||
s8 input_gamepad_id = -1, output_gamepad_id = -1; // -1 means it's not specified
|
||||
|
||||
// input gamepad id is only for controllers, it's discarded otherwise
|
||||
std::size_t input_colon_pos = input_string.find(':');
|
||||
if (input_colon_pos != std::string::npos) {
|
||||
auto temp = parseInt(input_string.substr(input_colon_pos + 1));
|
||||
if (!temp) {
|
||||
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
|
||||
} else {
|
||||
input_gamepad_id = *temp;
|
||||
}
|
||||
input_string = input_string.substr(0, input_colon_pos);
|
||||
}
|
||||
|
||||
// if not provided, assume it's for all gamepads, if the input is a controller and that also
|
||||
// doesn't have an ID, and for the first otherwise
|
||||
std::size_t output_colon_pos = output_string.find(':');
|
||||
if (output_colon_pos != std::string::npos) {
|
||||
auto temp = parseInt(output_string.substr(output_colon_pos + 1));
|
||||
if (!temp) {
|
||||
LOG_WARNING(Input, "Invalid gamepad ID value at line {}: \"{}\"", lineCount, line);
|
||||
} else {
|
||||
output_gamepad_id = *temp;
|
||||
}
|
||||
output_string = output_string.substr(0, output_colon_pos);
|
||||
}
|
||||
|
||||
std::size_t comma_pos = input_string.find(',');
|
||||
auto parseInt = [](const std::string& s) -> std::optional<int> {
|
||||
try {
|
||||
return std::stoi(s);
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
// todo make override_controller_color and analog_deadzone be controller specific
|
||||
// instead of global
|
||||
if (output_string == "mouse_to_joystick") {
|
||||
if (input_string == "left") {
|
||||
SetMouseToJoystick(1);
|
||||
@ -315,7 +450,7 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
return;
|
||||
}
|
||||
ControllerOutput* toggle_out =
|
||||
&*std::ranges::find(output_array, ControllerOutput(KEY_TOGGLE));
|
||||
&*std::ranges::find(output_arrays[0].data, ControllerOutput(KEY_TOGGLE));
|
||||
BindingConnection toggle_connection = BindingConnection(
|
||||
InputBinding(toggle_keys.keys[0]), toggle_out, 0, toggle_keys.keys[1]);
|
||||
connections.insert(connections.end(), toggle_connection);
|
||||
@ -356,15 +491,17 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
|
||||
std::pair<int, int> deadzone = {*inner_deadzone, *outer_deadzone};
|
||||
|
||||
static std::unordered_map<std::string, std::pair<int, int>&> deadzone_map = {
|
||||
{"leftjoystick", leftjoystick_deadzone},
|
||||
{"rightjoystick", rightjoystick_deadzone},
|
||||
{"l2", lefttrigger_deadzone},
|
||||
{"r2", righttrigger_deadzone},
|
||||
};
|
||||
static std::unordered_map<std::string, std::array<std::pair<int, int>, 4>&>
|
||||
deadzone_map = {
|
||||
{"leftjoystick", leftjoystick_deadzone},
|
||||
{"rightjoystick", rightjoystick_deadzone},
|
||||
{"l2", lefttrigger_deadzone},
|
||||
{"r2", righttrigger_deadzone},
|
||||
};
|
||||
output_gamepad_id = output_gamepad_id == -1 ? 1 : output_gamepad_id;
|
||||
|
||||
if (auto it = deadzone_map.find(device); it != deadzone_map.end()) {
|
||||
it->second = deadzone;
|
||||
it->second[output_gamepad_id - 1] = deadzone;
|
||||
LOG_DEBUG(Input, "Parsed deadzone: {} {} {}", device, inner_deadzone_str,
|
||||
outer_deadzone_str);
|
||||
} else {
|
||||
@ -390,10 +527,12 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
lineCount, line);
|
||||
return;
|
||||
}
|
||||
Config::SetOverrideControllerColor(enable == "true");
|
||||
Config::SetControllerCustomColor(*r, *g, *b);
|
||||
LOG_DEBUG(Input, "Parsed color settings: {} {} {} {}",
|
||||
enable == "true" ? "override" : "no override", *r, *b, *g);
|
||||
output_gamepad_id = output_gamepad_id == -1 ? 1 : output_gamepad_id;
|
||||
if (enable == "true") {
|
||||
GameControllers::SetControllerCustomColor(output_gamepad_id - 1, *r, *g, *b);
|
||||
}
|
||||
LOG_DEBUG(Input, "Parsed color settings: {} {} - {} {} {}",
|
||||
enable == "true" ? "override" : "no override", output_gamepad_id, *r, *b, *g);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -410,31 +549,46 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
auto axis_it = string_to_axis_map.find(output_string);
|
||||
if (button_it != string_to_cbutton_map.end()) {
|
||||
connection = BindingConnection(
|
||||
binding, &*std::ranges::find(output_array, ControllerOutput(button_it->second)));
|
||||
connections.insert(connections.end(), connection);
|
||||
binding,
|
||||
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
|
||||
ControllerOutput(button_it->second)));
|
||||
} else if (hotkey_it != string_to_hotkey_map.end()) {
|
||||
connection = BindingConnection(
|
||||
binding, &*std::ranges::find(output_array, ControllerOutput(hotkey_it->second)));
|
||||
connections.insert(connections.end(), connection);
|
||||
binding,
|
||||
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
|
||||
ControllerOutput(hotkey_it->second)));
|
||||
} else if (axis_it != string_to_axis_map.end()) {
|
||||
int value_to_set = binding.keys[2].type == InputType::Axis ? 0 : axis_it->second.value;
|
||||
connection = BindingConnection(
|
||||
binding,
|
||||
&*std::ranges::find(output_array, ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
|
||||
axis_it->second.axis,
|
||||
axis_it->second.value >= 0)),
|
||||
&*std::ranges::find(output_arrays[std::clamp(output_gamepad_id - 1, 0, 3)].data,
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID,
|
||||
axis_it->second.axis,
|
||||
axis_it->second.value >= 0)),
|
||||
value_to_set);
|
||||
connections.insert(connections.end(), connection);
|
||||
} else {
|
||||
LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.",
|
||||
lineCount, line);
|
||||
return;
|
||||
}
|
||||
// if the input binding contains a controller input, and gamepad ID
|
||||
// isn't specified for either inputs or output (both are -1), then multiply the binding and
|
||||
// add it to all 4 controllers
|
||||
if (connection.HasGamepadInput() && input_gamepad_id == -1 && output_gamepad_id == -1) {
|
||||
for (int i = 0; i < output_arrays.size(); i++) {
|
||||
BindingConnection copy = connection.CopyWithChangedGamepadId(i + 1);
|
||||
copy.output = &*std::ranges::find(output_arrays[i].data, *connection.output);
|
||||
connections.push_back(copy);
|
||||
}
|
||||
} else {
|
||||
connections.push_back(connection);
|
||||
}
|
||||
LOG_DEBUG(Input, "Succesfully parsed line {}", lineCount);
|
||||
};
|
||||
while (std::getline(global_config_stream, line)) {
|
||||
ProcessLine();
|
||||
}
|
||||
lineCount = 0;
|
||||
while (std::getline(config_stream, line)) {
|
||||
ProcessLine();
|
||||
}
|
||||
@ -446,6 +600,16 @@ void ParseInputConfig(const std::string game_id = "") {
|
||||
LOG_DEBUG(Input, "Done parsing the input config!");
|
||||
}
|
||||
|
||||
BindingConnection BindingConnection::CopyWithChangedGamepadId(u8 gamepad) {
|
||||
BindingConnection copy = *this;
|
||||
for (auto& key : copy.binding.keys) {
|
||||
if (key.type == InputType::Controller || key.type == InputType::Axis) {
|
||||
key.gamepad_id = gamepad;
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
u32 GetMouseWheelEvent(const SDL_Event& event) {
|
||||
if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) {
|
||||
LOG_WARNING(Input, "Something went wrong with wheel input parsing!");
|
||||
@ -464,6 +628,7 @@ u32 GetMouseWheelEvent(const SDL_Event& event) {
|
||||
}
|
||||
|
||||
InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
|
||||
u8 gamepad = 1;
|
||||
switch (e.type) {
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
case SDL_EVENT_KEY_UP:
|
||||
@ -478,21 +643,17 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) {
|
||||
e.type == SDL_EVENT_MOUSE_WHEEL, 0);
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
return InputEvent(InputType::Controller, static_cast<u32>(e.gbutton.button), e.gbutton.down,
|
||||
0); // clang made me do it
|
||||
gamepad = ControllerOutput::controllers.GetGamepadIndexFromJoystickId(e.gbutton.which) + 1;
|
||||
return InputEvent({InputType::Controller, (u32)e.gbutton.button, gamepad}, e.gbutton.down,
|
||||
0);
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
return InputEvent(InputType::Axis, static_cast<u32>(e.gaxis.axis), true,
|
||||
e.gaxis.value / 256); // this too
|
||||
gamepad = ControllerOutput::controllers.GetGamepadIndexFromJoystickId(e.gaxis.which) + 1;
|
||||
return InputEvent({InputType::Axis, (u32)e.gaxis.axis, gamepad}, true, e.gaxis.value / 256);
|
||||
default:
|
||||
return InputEvent();
|
||||
}
|
||||
}
|
||||
|
||||
GameController* ControllerOutput::controller = nullptr;
|
||||
void ControllerOutput::SetControllerOutputController(GameController* c) {
|
||||
ControllerOutput::controller = c;
|
||||
}
|
||||
|
||||
void ToggleKeyInList(InputID input) {
|
||||
if (input.type == InputType::Axis) {
|
||||
LOG_ERROR(Input, "Toggling analog inputs is not supported!");
|
||||
@ -538,7 +699,7 @@ void ControllerOutput::AddUpdate(InputEvent event) {
|
||||
*new_param = (event.active ? event.axis_value : 0) + *new_param;
|
||||
}
|
||||
}
|
||||
void ControllerOutput::FinalizeUpdate() {
|
||||
void ControllerOutput::FinalizeUpdate(u8 gamepad_index) {
|
||||
auto PushSDLEvent = [&](u32 event_type) {
|
||||
if (new_button_state) {
|
||||
SDL_Event e;
|
||||
@ -553,20 +714,24 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
}
|
||||
old_button_state = new_button_state;
|
||||
old_param = *new_param;
|
||||
bool is_game_specific = EmulatorState::GetInstance()->IsGameSpecifigConfigUsed();
|
||||
GameController* controller;
|
||||
if (gamepad_index < 5)
|
||||
controller = controllers[gamepad_index];
|
||||
else
|
||||
UNREACHABLE();
|
||||
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
|
||||
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
|
||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
|
||||
break;
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
|
||||
controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f);
|
||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
|
||||
break;
|
||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
|
||||
controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f);
|
||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
|
||||
break;
|
||||
case LEFTJOYSTICK_HALFMODE:
|
||||
leftjoystick_halfmode = new_button_state;
|
||||
@ -598,6 +763,12 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
case HOTKEY_RENDERDOC:
|
||||
PushSDLEvent(SDL_EVENT_RDOC_CAPTURE);
|
||||
break;
|
||||
case HOTKEY_ADD_VIRTUAL_USER:
|
||||
PushSDLEvent(SDL_EVENT_ADD_VIRTUAL_USER);
|
||||
break;
|
||||
case HOTKEY_REMOVE_VIRTUAL_USER:
|
||||
PushSDLEvent(SDL_EVENT_REMOVE_VIRTUAL_USER);
|
||||
break;
|
||||
case HOTKEY_VOLUME_UP:
|
||||
EmulatorSettings.SetVolumeSlider(
|
||||
std::clamp(EmulatorSettings.GetVolumeSlider() + 10, 0, 500));
|
||||
@ -618,7 +789,7 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
SetMouseGyroRollMode(new_button_state);
|
||||
break;
|
||||
default: // is a normal key (hopefully)
|
||||
controller->Button(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||
controller->Button(SDLGamepadToOrbisButton(button), new_button_state);
|
||||
break;
|
||||
}
|
||||
} else if (axis != SDL_GAMEPAD_AXIS_INVALID && positive_axis) {
|
||||
@ -638,28 +809,28 @@ void ControllerOutput::FinalizeUpdate() {
|
||||
switch (c_axis) {
|
||||
case Axis::LeftX:
|
||||
case Axis::LeftY:
|
||||
ApplyDeadzone(new_param, leftjoystick_deadzone);
|
||||
ApplyDeadzone(new_param, leftjoystick_deadzone[gamepad_index]);
|
||||
multiplier = leftjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::RightX:
|
||||
case Axis::RightY:
|
||||
ApplyDeadzone(new_param, rightjoystick_deadzone);
|
||||
ApplyDeadzone(new_param, rightjoystick_deadzone[gamepad_index]);
|
||||
multiplier = rightjoystick_halfmode ? 0.5 : 1.0;
|
||||
break;
|
||||
case Axis::TriggerLeft:
|
||||
ApplyDeadzone(new_param, lefttrigger_deadzone);
|
||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controller->Button(0, OrbisPadButtonDataOffset::L2, *new_param > 0x20);
|
||||
ApplyDeadzone(new_param, lefttrigger_deadzone[gamepad_index]);
|
||||
controller->Axis(c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controller->Button(OrbisPadButtonDataOffset::L2, *new_param > 0x20);
|
||||
return;
|
||||
case Axis::TriggerRight:
|
||||
ApplyDeadzone(new_param, righttrigger_deadzone);
|
||||
controller->Axis(0, c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controller->Button(0, OrbisPadButtonDataOffset::R2, *new_param > 0x20);
|
||||
ApplyDeadzone(new_param, righttrigger_deadzone[gamepad_index]);
|
||||
controller->Axis(c_axis, GetAxis(0x0, 0x7f, *new_param));
|
||||
controller->Button(OrbisPadButtonDataOffset::R2, *new_param > 0x20);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
controller->Axis(0, c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
|
||||
controller->Axis(c_axis, GetAxis(-0x80, 0x7f, *new_param * multiplier));
|
||||
}
|
||||
}
|
||||
|
||||
@ -674,11 +845,9 @@ bool UpdatePressedKeys(InputEvent event) {
|
||||
if (input.type == InputType::Axis) {
|
||||
// analog input, it gets added when it first sends an event,
|
||||
// and from there, it only changes the parameter
|
||||
auto it = std::lower_bound(pressed_keys.begin(), pressed_keys.end(), input,
|
||||
[](const std::pair<InputEvent, bool>& e, InputID i) {
|
||||
return std::tie(e.first.input.type, e.first.input.sdl_id) <
|
||||
std::tie(i.type, i.sdl_id);
|
||||
});
|
||||
auto it = std::lower_bound(
|
||||
pressed_keys.begin(), pressed_keys.end(), input,
|
||||
[](const std::pair<InputEvent, bool>& e, InputID i) { return e.first.input < i; });
|
||||
if (it == pressed_keys.end() || it->first.input != input) {
|
||||
pressed_keys.insert(it, {event, false});
|
||||
LOG_DEBUG(Input, "Added axis {} to the input list", event.input.sdl_id);
|
||||
@ -791,25 +960,33 @@ InputEvent BindingConnection::ProcessBinding() {
|
||||
}
|
||||
|
||||
void ActivateOutputsFromInputs() {
|
||||
// Reset values and flags
|
||||
for (auto& it : pressed_keys) {
|
||||
it.second = false;
|
||||
}
|
||||
for (auto& it : output_array) {
|
||||
it.ResetUpdate();
|
||||
}
|
||||
|
||||
// Check for input blockers
|
||||
ApplyMouseInputBlockers();
|
||||
// todo find a better solution
|
||||
for (int i = 0; i < output_arrays.size(); i++) {
|
||||
|
||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
||||
for (auto& it : connections) {
|
||||
it.output->AddUpdate(it.ProcessBinding());
|
||||
}
|
||||
// Reset values and flags
|
||||
for (auto& it : pressed_keys) {
|
||||
it.second = false;
|
||||
}
|
||||
for (auto& it : output_arrays[i].data) {
|
||||
it.ResetUpdate();
|
||||
}
|
||||
|
||||
// Update all outputs
|
||||
for (auto& it : output_array) {
|
||||
it.FinalizeUpdate();
|
||||
// Check for input blockers
|
||||
ApplyMouseInputBlockers();
|
||||
|
||||
// Iterate over all inputs, and update their respecive outputs accordingly
|
||||
for (auto& it : connections) {
|
||||
// only update this when it's the correct pass
|
||||
if (it.output->gamepad_id == i) {
|
||||
it.output->AddUpdate(it.ProcessBinding());
|
||||
}
|
||||
}
|
||||
|
||||
// Update all outputs
|
||||
for (auto& it : output_arrays[i].data) {
|
||||
it.FinalizeUpdate(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "SDL3/SDL_events.h"
|
||||
#include "SDL3/SDL_timer.h"
|
||||
@ -35,9 +36,11 @@
|
||||
#define SDL_EVENT_MOUSE_TO_JOYSTICK SDL_EVENT_USER + 6
|
||||
#define SDL_EVENT_MOUSE_TO_GYRO SDL_EVENT_USER + 7
|
||||
#define SDL_EVENT_MOUSE_TO_TOUCHPAD SDL_EVENT_USER + 8
|
||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 9
|
||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 10
|
||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 11
|
||||
#define SDL_EVENT_QUIT_DIALOG SDL_EVENT_USER + 9
|
||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
||||
#define SDL_EVENT_ADD_VIRTUAL_USER SDL_EVENT_USER + 11
|
||||
#define SDL_EVENT_REMOVE_VIRTUAL_USER SDL_EVENT_USER + 12
|
||||
#define SDL_EVENT_RDOC_CAPTURE SDL_EVENT_USER + 13
|
||||
|
||||
#define LEFTJOYSTICK_HALFMODE 0x00010000
|
||||
#define RIGHTJOYSTICK_HALFMODE 0x00020000
|
||||
@ -57,6 +60,8 @@
|
||||
#define HOTKEY_RENDERDOC 0xf0000009
|
||||
#define HOTKEY_VOLUME_UP 0xf000000a
|
||||
#define HOTKEY_VOLUME_DOWN 0xf000000b
|
||||
#define HOTKEY_ADD_VIRTUAL_USER 0xf000000c
|
||||
#define HOTKEY_REMOVE_VIRTUAL_USER 0xf000000d
|
||||
|
||||
#define SDL_UNMAPPED UINT32_MAX - 1
|
||||
|
||||
@ -77,21 +82,24 @@ class InputID {
|
||||
public:
|
||||
InputType type;
|
||||
u32 sdl_id;
|
||||
InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {}
|
||||
u8 gamepad_id;
|
||||
InputID(InputType d = InputType::Count, u32 i = (u32)-1, u8 g = 1)
|
||||
: type(d), sdl_id(i), gamepad_id(g) {}
|
||||
bool operator==(const InputID& o) const {
|
||||
return type == o.type && sdl_id == o.sdl_id;
|
||||
return type == o.type && sdl_id == o.sdl_id && gamepad_id == o.gamepad_id;
|
||||
}
|
||||
bool operator!=(const InputID& o) const {
|
||||
return type != o.type || sdl_id != o.sdl_id;
|
||||
return type != o.type || sdl_id != o.sdl_id || gamepad_id != o.gamepad_id;
|
||||
}
|
||||
bool operator<=(const InputID& o) const {
|
||||
return type <= o.type && sdl_id <= o.sdl_id;
|
||||
auto operator<=>(const InputID& o) const {
|
||||
return std::tie(gamepad_id, type, sdl_id, gamepad_id) <=>
|
||||
std::tie(o.gamepad_id, o.type, o.sdl_id, o.gamepad_id);
|
||||
}
|
||||
bool IsValid() const {
|
||||
return *this != InputID();
|
||||
}
|
||||
std::string ToString() {
|
||||
return fmt::format("({}: {:x})", input_type_names[static_cast<u8>(type)], sdl_id);
|
||||
return fmt::format("({}. {}: {:x})", gamepad_id, input_type_names[(u8)type], sdl_id);
|
||||
}
|
||||
};
|
||||
|
||||
@ -149,6 +157,8 @@ const std::map<std::string, u32> string_to_hotkey_map = {
|
||||
{"hotkey_toggle_mouse_to_gyro", HOTKEY_TOGGLE_MOUSE_TO_GYRO},
|
||||
{"hotkey_toggle_mouse_to_touchpad", HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD},
|
||||
{"hotkey_renderdoc_capture", HOTKEY_RENDERDOC},
|
||||
{"hotkey_add_virtual_user", HOTKEY_ADD_VIRTUAL_USER},
|
||||
{"hotkey_remove_virtual_user", HOTKEY_REMOVE_VIRTUAL_USER},
|
||||
{"hotkey_volume_up", HOTKEY_VOLUME_UP},
|
||||
{"hotkey_volume_down", HOTKEY_VOLUME_DOWN},
|
||||
};
|
||||
@ -401,7 +411,7 @@ public:
|
||||
inline bool IsEmpty() {
|
||||
return !(keys[0].IsValid() || keys[1].IsValid() || keys[2].IsValid());
|
||||
}
|
||||
std::string ToString() { // todo add device type
|
||||
std::string ToString() {
|
||||
switch (KeyCount()) {
|
||||
case 1:
|
||||
return fmt::format("({})", keys[0].ToString());
|
||||
@ -420,14 +430,14 @@ public:
|
||||
};
|
||||
|
||||
class ControllerOutput {
|
||||
static GameController* controller;
|
||||
|
||||
public:
|
||||
static void SetControllerOutputController(GameController* c);
|
||||
static GameControllers controllers;
|
||||
static void GetGetGamepadIndexFromSDLJoystickID(const SDL_JoystickID id) {}
|
||||
static void LinkJoystickAxes();
|
||||
|
||||
u32 button;
|
||||
u32 axis;
|
||||
u8 gamepad_id;
|
||||
// these are only used as s8,
|
||||
// but I added some padding to avoid overflow if it's activated by multiple inputs
|
||||
// axis_plus and axis_minus pairs share a common new_param, the other outputs have their own
|
||||
@ -441,6 +451,7 @@ public:
|
||||
new_param = new s16(0);
|
||||
old_param = 0;
|
||||
positive_axis = p;
|
||||
gamepad_id = 0;
|
||||
}
|
||||
ControllerOutput(const ControllerOutput& o) : button(o.button), axis(o.axis) {
|
||||
new_param = new s16(*o.new_param);
|
||||
@ -466,7 +477,7 @@ public:
|
||||
|
||||
void ResetUpdate();
|
||||
void AddUpdate(InputEvent event);
|
||||
void FinalizeUpdate();
|
||||
void FinalizeUpdate(u8 gamepad_index);
|
||||
};
|
||||
class BindingConnection {
|
||||
public:
|
||||
@ -481,6 +492,13 @@ public:
|
||||
output = out;
|
||||
toggle = t;
|
||||
}
|
||||
BindingConnection& operator=(const BindingConnection& o) {
|
||||
binding = o.binding;
|
||||
output = o.output;
|
||||
axis_param = o.axis_param;
|
||||
toggle = o.toggle;
|
||||
return *this;
|
||||
}
|
||||
bool operator<(const BindingConnection& other) const {
|
||||
// a button is a higher priority than an axis, as buttons can influence axes
|
||||
// (e.g. joystick_halfmode)
|
||||
@ -494,9 +512,82 @@ public:
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool HasGamepadInput() {
|
||||
for (auto& key : binding.keys) {
|
||||
if (key.type == InputType::Controller || key.type == InputType::Axis) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
BindingConnection CopyWithChangedGamepadId(u8 gamepad);
|
||||
InputEvent ProcessBinding();
|
||||
};
|
||||
|
||||
class ControllerAllOutputs {
|
||||
public:
|
||||
static constexpr u64 output_count = 40;
|
||||
std::array<ControllerOutput, output_count> data = {
|
||||
// Important: these have to be the first, or else they will update in the wrong order
|
||||
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||
ControllerOutput(KEY_TOGGLE),
|
||||
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
|
||||
|
||||
// Button mappings
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
||||
|
||||
// Axis mappings
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX, false),
|
||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY, false),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTY),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTX),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHTY),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFT_TRIGGER),
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER),
|
||||
|
||||
ControllerOutput(HOTKEY_FULLSCREEN),
|
||||
ControllerOutput(HOTKEY_PAUSE),
|
||||
ControllerOutput(HOTKEY_SIMPLE_FPS),
|
||||
ControllerOutput(HOTKEY_QUIT),
|
||||
ControllerOutput(HOTKEY_RELOAD_INPUTS),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_JOYSTICK),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_GYRO),
|
||||
ControllerOutput(HOTKEY_TOGGLE_MOUSE_TO_TOUCHPAD),
|
||||
ControllerOutput(HOTKEY_RENDERDOC),
|
||||
ControllerOutput(HOTKEY_ADD_VIRTUAL_USER),
|
||||
ControllerOutput(HOTKEY_REMOVE_VIRTUAL_USER),
|
||||
ControllerOutput(HOTKEY_VOLUME_UP),
|
||||
ControllerOutput(HOTKEY_VOLUME_DOWN),
|
||||
|
||||
ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_INVALID),
|
||||
};
|
||||
ControllerAllOutputs(u8 g) {
|
||||
for (int i = 0; i < output_count; i++) {
|
||||
data[i].gamepad_id = g;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Updates the list of pressed keys with the given input.
|
||||
// Returns whether the list was updated or not.
|
||||
bool UpdatePressedKeys(InputEvent event);
|
||||
|
||||
@ -77,11 +77,11 @@ void EmulateJoystick(GameController* controller, u32 interval) {
|
||||
float a_x = cos(angle) * output_speed, a_y = sin(angle) * output_speed;
|
||||
|
||||
if (d_x != 0 || d_y != 0) {
|
||||
controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, a_x));
|
||||
controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, a_y));
|
||||
controller->Axis(axis_x, GetAxis(-0x80, 0x7f, a_x), false);
|
||||
controller->Axis(axis_y, GetAxis(-0x80, 0x7f, a_y), false);
|
||||
} else {
|
||||
controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0));
|
||||
controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0));
|
||||
controller->Axis(axis_x, GetAxis(-0x80, 0x7f, 0), false);
|
||||
controller->Axis(axis_y, GetAxis(-0x80, 0x7f, 0), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,13 +89,13 @@ constexpr float constant_down_accel[3] = {0.0f, 9.81f, 0.0f};
|
||||
void EmulateGyro(GameController* controller, u32 interval) {
|
||||
float d_x = 0, d_y = 0;
|
||||
SDL_GetRelativeMouseState(&d_x, &d_y);
|
||||
controller->Acceleration(1, constant_down_accel);
|
||||
controller->UpdateAcceleration(constant_down_accel);
|
||||
float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f};
|
||||
if (mouse_gyro_roll_mode) {
|
||||
gyro_from_mouse[1] = 0.0f;
|
||||
gyro_from_mouse[2] = -d_x / 100;
|
||||
}
|
||||
controller->Gyro(1, gyro_from_mouse);
|
||||
controller->UpdateGyro(gyro_from_mouse);
|
||||
}
|
||||
|
||||
void EmulateTouchpad(GameController* controller, u32 interval) {
|
||||
@ -104,7 +104,7 @@ void EmulateTouchpad(GameController* controller, u32 interval) {
|
||||
controller->SetTouchpadState(0, (mouse_buttons & SDL_BUTTON_LMASK) != 0,
|
||||
std::clamp(x / g_window->GetWidth(), 0.0f, 1.0f),
|
||||
std::clamp(y / g_window->GetHeight(), 0.0f, 1.0f));
|
||||
controller->Button(0, Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
|
||||
controller->Button(Libraries::Pad::OrbisPadButtonDataOffset::TouchPad,
|
||||
(mouse_buttons & SDL_BUTTON_RMASK) != 0);
|
||||
}
|
||||
|
||||
|
||||
@ -8,12 +8,14 @@
|
||||
#include "SDL3/SDL_timer.h"
|
||||
#include "SDL3/SDL_video.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/config.h"
|
||||
#include "common/elf_info.h"
|
||||
#include "core/debug_state.h"
|
||||
#include "core/devtools/layer.h"
|
||||
#include "core/emulator_settings.h"
|
||||
#include "core/libraries/kernel/time.h"
|
||||
#include "core/libraries/pad/pad.h"
|
||||
#include "core/libraries/system/userservice.h"
|
||||
#include "core/user_settings.h"
|
||||
#include "imgui/renderer/imgui_core.h"
|
||||
#include "input/controller.h"
|
||||
#include "input/input_handler.h"
|
||||
@ -26,9 +28,9 @@
|
||||
#endif
|
||||
#include <core/emulator_settings.h>
|
||||
|
||||
namespace Input {
|
||||
namespace Frontend {
|
||||
|
||||
using Libraries::Pad::OrbisPadButtonDataOffset;
|
||||
using namespace Libraries::Pad;
|
||||
|
||||
static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
||||
using OPBDO = OrbisPadButtonDataOffset;
|
||||
@ -69,220 +71,24 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
||||
}
|
||||
}
|
||||
|
||||
static SDL_GamepadAxis InputAxisToSDL(Axis axis) {
|
||||
switch (axis) {
|
||||
case Axis::LeftX:
|
||||
return SDL_GAMEPAD_AXIS_LEFTX;
|
||||
case Axis::LeftY:
|
||||
return SDL_GAMEPAD_AXIS_LEFTY;
|
||||
case Axis::RightX:
|
||||
return SDL_GAMEPAD_AXIS_RIGHTX;
|
||||
case Axis::RightY:
|
||||
return SDL_GAMEPAD_AXIS_RIGHTY;
|
||||
case Axis::TriggerLeft:
|
||||
return SDL_GAMEPAD_AXIS_LEFT_TRIGGER;
|
||||
case Axis::TriggerRight:
|
||||
return SDL_GAMEPAD_AXIS_RIGHT_TRIGGER;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
SDLInputEngine::~SDLInputEngine() {
|
||||
if (m_gamepad) {
|
||||
SDL_CloseGamepad(m_gamepad);
|
||||
}
|
||||
}
|
||||
|
||||
void SDLInputEngine::Init() {
|
||||
if (m_gamepad) {
|
||||
SDL_CloseGamepad(m_gamepad);
|
||||
m_gamepad = nullptr;
|
||||
}
|
||||
|
||||
int gamepad_count;
|
||||
SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count);
|
||||
if (!gamepads) {
|
||||
LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError());
|
||||
return;
|
||||
}
|
||||
if (gamepad_count == 0) {
|
||||
LOG_INFO(Input, "No gamepad found!");
|
||||
SDL_free(gamepads);
|
||||
return;
|
||||
}
|
||||
|
||||
int selectedIndex = GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count,
|
||||
GamepadSelect::GetSelectedGamepad());
|
||||
int defaultIndex =
|
||||
GamepadSelect::GetIndexfromGUID(gamepads, gamepad_count, Config::getDefaultControllerID());
|
||||
|
||||
// If user selects a gamepad in the GUI, use that, otherwise try the default
|
||||
if (!m_gamepad) {
|
||||
if (selectedIndex != -1) {
|
||||
m_gamepad = SDL_OpenGamepad(gamepads[selectedIndex]);
|
||||
LOG_INFO(Input, "Opening gamepad selected in GUI.");
|
||||
} else if (defaultIndex != -1) {
|
||||
m_gamepad = SDL_OpenGamepad(gamepads[defaultIndex]);
|
||||
LOG_INFO(Input, "Opening default gamepad.");
|
||||
} else {
|
||||
m_gamepad = SDL_OpenGamepad(gamepads[0]);
|
||||
LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_gamepad) {
|
||||
if (!m_gamepad) {
|
||||
LOG_ERROR(Input, "Failed to open gamepad: {}", SDL_GetError());
|
||||
SDL_free(gamepads);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Joystick* joystick = SDL_GetGamepadJoystick(m_gamepad);
|
||||
Uint16 vendor = SDL_GetJoystickVendor(joystick);
|
||||
Uint16 product = SDL_GetJoystickProduct(joystick);
|
||||
|
||||
bool isDualSense = (vendor == 0x054C && product == 0x0CE6);
|
||||
|
||||
LOG_INFO(Input, "Gamepad Vendor: {:04X}, Product: {:04X}", vendor, product);
|
||||
if (isDualSense) {
|
||||
LOG_INFO(Input, "Detected DualSense Controller");
|
||||
}
|
||||
|
||||
if (Config::getIsMotionControlsEnabled()) {
|
||||
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, true)) {
|
||||
m_gyro_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_GYRO);
|
||||
LOG_INFO(Input, "Gyro initialized, poll rate: {}", m_gyro_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize gyro controls for gamepad, error: {}",
|
||||
SDL_GetError());
|
||||
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_GYRO, false);
|
||||
}
|
||||
if (SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, true)) {
|
||||
m_accel_poll_rate = SDL_GetGamepadSensorDataRate(m_gamepad, SDL_SENSOR_ACCEL);
|
||||
LOG_INFO(Input, "Accel initialized, poll rate: {}", m_accel_poll_rate);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to initialize accel controls for gamepad, error: {}",
|
||||
SDL_GetError());
|
||||
SDL_SetGamepadSensorEnabled(m_gamepad, SDL_SENSOR_ACCEL, false);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_free(gamepads);
|
||||
|
||||
int* rgb = Config::GetControllerCustomColor();
|
||||
|
||||
if (isDualSense) {
|
||||
if (SDL_SetJoystickLED(joystick, rgb[0], rgb[1], rgb[2]) == 0) {
|
||||
LOG_INFO(Input, "Set DualSense LED to R:{} G:{} B:{}", rgb[0], rgb[1], rgb[2]);
|
||||
} else {
|
||||
LOG_ERROR(Input, "Failed to set DualSense LED: {}", SDL_GetError());
|
||||
}
|
||||
} else {
|
||||
SetLightBarRGB(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
}
|
||||
|
||||
void SDLInputEngine::SetLightBarRGB(u8 r, u8 g, u8 b) {
|
||||
if (m_gamepad) {
|
||||
SDL_SetGamepadLED(m_gamepad, r, g, b);
|
||||
}
|
||||
}
|
||||
|
||||
void SDLInputEngine::SetVibration(u8 smallMotor, u8 largeMotor) {
|
||||
if (m_gamepad) {
|
||||
const auto low_freq = (smallMotor / 255.0f) * 0xFFFF;
|
||||
const auto high_freq = (largeMotor / 255.0f) * 0xFFFF;
|
||||
SDL_RumbleGamepad(m_gamepad, low_freq, high_freq, -1);
|
||||
}
|
||||
}
|
||||
|
||||
State SDLInputEngine::ReadState() {
|
||||
State state{};
|
||||
state.time = Libraries::Kernel::sceKernelGetProcessTime();
|
||||
|
||||
// Buttons
|
||||
for (u8 i = 0; i < SDL_GAMEPAD_BUTTON_COUNT; ++i) {
|
||||
auto orbisButton = SDLGamepadToOrbisButton(i);
|
||||
if (orbisButton == OrbisPadButtonDataOffset::None) {
|
||||
continue;
|
||||
}
|
||||
state.OnButton(orbisButton, SDL_GetGamepadButton(m_gamepad, (SDL_GamepadButton)i));
|
||||
}
|
||||
|
||||
// Axes
|
||||
for (int i = 0; i < static_cast<int>(Axis::AxisMax); ++i) {
|
||||
const auto axis = static_cast<Axis>(i);
|
||||
const auto value = SDL_GetGamepadAxis(m_gamepad, InputAxisToSDL(axis));
|
||||
switch (axis) {
|
||||
case Axis::TriggerLeft:
|
||||
case Axis::TriggerRight:
|
||||
state.OnAxis(axis, GetAxis(0, 0x8000, value));
|
||||
break;
|
||||
default:
|
||||
state.OnAxis(axis, GetAxis(-0x8000, 0x8000, value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Touchpad
|
||||
if (SDL_GetNumGamepadTouchpads(m_gamepad) > 0) {
|
||||
for (int finger = 0; finger < 2; ++finger) {
|
||||
bool down;
|
||||
float x, y;
|
||||
if (SDL_GetGamepadTouchpadFinger(m_gamepad, 0, finger, &down, &x, &y, NULL)) {
|
||||
state.OnTouchpad(finger, down, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gyro
|
||||
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_GYRO)) {
|
||||
float gyro[3];
|
||||
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_GYRO, gyro, 3)) {
|
||||
state.OnGyro(gyro);
|
||||
}
|
||||
}
|
||||
|
||||
// Accel
|
||||
if (SDL_GamepadHasSensor(m_gamepad, SDL_SENSOR_ACCEL)) {
|
||||
float accel[3];
|
||||
if (SDL_GetGamepadSensorData(m_gamepad, SDL_SENSOR_ACCEL, accel, 3)) {
|
||||
state.OnAccel(accel);
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
float SDLInputEngine::GetGyroPollRate() const {
|
||||
return m_gyro_poll_rate;
|
||||
}
|
||||
|
||||
float SDLInputEngine::GetAccelPollRate() const {
|
||||
return m_accel_poll_rate;
|
||||
}
|
||||
|
||||
} // namespace Input
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
using namespace Libraries::Pad;
|
||||
|
||||
std::mutex motion_control_mutex;
|
||||
float gyro_buf[3] = {0.0f, 0.0f, 0.0f}, accel_buf[3] = {0.0f, 9.81f, 0.0f};
|
||||
static Uint32 SDLCALL PollGyroAndAccel(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
|
||||
static Uint32 SDLCALL PollController(void* userdata, SDL_TimerID timer_id, Uint32 interval) {
|
||||
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
||||
std::scoped_lock l{motion_control_mutex};
|
||||
controller->Gyro(0, gyro_buf);
|
||||
controller->Acceleration(0, accel_buf);
|
||||
return 4;
|
||||
controller->UpdateAxisSmoothing();
|
||||
controller->Gyro(0);
|
||||
controller->Acceleration(0);
|
||||
return interval;
|
||||
}
|
||||
|
||||
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_,
|
||||
static Uint32 SDLCALL PollControllerLightColour(void* userdata, SDL_TimerID timer_id,
|
||||
Uint32 interval) {
|
||||
auto* controller = reinterpret_cast<Input::GameController*>(userdata);
|
||||
controller->PollLightColour();
|
||||
return interval;
|
||||
}
|
||||
|
||||
WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameControllers* controllers_,
|
||||
std::string_view window_title)
|
||||
: width{width_}, height{height_}, controller{controller_} {
|
||||
: width{width_}, height{height_}, controllers{*controllers_} {
|
||||
if (!SDL_SetHint(SDL_HINT_APP_NAME, "shadPS4")) {
|
||||
UNREACHABLE_MSG("Failed to set SDL window hint: {}", SDL_GetError());
|
||||
}
|
||||
@ -290,7 +96,7 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* 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);
|
||||
|
||||
@ -330,13 +136,13 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
|
||||
SDL_SyncWindow(window);
|
||||
|
||||
SDL_InitSubSystem(SDL_INIT_GAMEPAD);
|
||||
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
|
||||
|
||||
#if defined(SDL_PLATFORM_WIN32)
|
||||
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(
|
||||
@ -355,11 +161,11 @@ WindowSDL::WindowSDL(s32 width_, s32 height_, Input::GameController* controller_
|
||||
window_info.render_surface = SDL_Metal_GetLayer(SDL_Metal_CreateView(window));
|
||||
#endif
|
||||
// input handler init-s
|
||||
Input::ControllerOutput::SetControllerOutputController(controller);
|
||||
Input::ControllerOutput::LinkJoystickAxes();
|
||||
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||
controllers.TryOpenSDLControllers();
|
||||
|
||||
if (Config::getBackgroundControllerInput()) {
|
||||
if (EmulatorSettings.IsBackgroundControllerInput()) {
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
}
|
||||
}
|
||||
@ -399,37 +205,16 @@ void WindowSDL::WaitEvent() {
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_ADDED:
|
||||
case SDL_EVENT_GAMEPAD_REMOVED:
|
||||
controller->SetEngine(std::make_unique<Input::SDLInputEngine>());
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
controller->SetTouchpadState(event.gtouchpad.finger,
|
||||
event.type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event.gtouchpad.x,
|
||||
event.gtouchpad.y);
|
||||
controllers.TryOpenSDLControllers();
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP:
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
|
||||
OnGamepadEvent(&event);
|
||||
break;
|
||||
// i really would have appreciated ANY KIND OF DOCUMENTATION ON THIS
|
||||
// AND IT DOESN'T EVEN USE PROPER ENUMS
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
||||
switch ((SDL_SensorType)event.gsensor.sensor) {
|
||||
case SDL_SENSOR_GYRO: {
|
||||
std::scoped_lock l{motion_control_mutex};
|
||||
memcpy(gyro_buf, event.gsensor.data, sizeof(gyro_buf));
|
||||
break;
|
||||
}
|
||||
case SDL_SENSOR_ACCEL: {
|
||||
std::scoped_lock l{motion_control_mutex};
|
||||
memcpy(accel_buf, event.gsensor.data, sizeof(accel_buf));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
OnGamepadEvent(&event);
|
||||
break;
|
||||
case SDL_EVENT_QUIT:
|
||||
is_open = false;
|
||||
@ -455,7 +240,7 @@ void WindowSDL::WaitEvent() {
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_CHANGE_CONTROLLER:
|
||||
controller->GetEngine()->Init();
|
||||
UNREACHABLE_MSG("todo");
|
||||
break;
|
||||
case SDL_EVENT_TOGGLE_SIMPLE_FPS:
|
||||
Overlay::ToggleSimpleFps();
|
||||
@ -476,6 +261,29 @@ void WindowSDL::WaitEvent() {
|
||||
Input::ToggleMouseModeTo(Input::MouseMode::Touchpad));
|
||||
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), false);
|
||||
break;
|
||||
case SDL_EVENT_ADD_VIRTUAL_USER:
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (controllers[i]->user_id == -1) {
|
||||
auto u = UserManagement.GetUserByPlayerIndex(i + 1);
|
||||
if (!u) {
|
||||
break;
|
||||
}
|
||||
controllers[i]->user_id = u->user_id;
|
||||
UserManagement.LoginUser(u, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_REMOVE_VIRTUAL_USER:
|
||||
LOG_INFO(Input, "Remove user");
|
||||
for (int i = 3; i >= 0; i--) {
|
||||
if (controllers[i]->user_id != -1) {
|
||||
UserManagement.LogoutUser(UserManagement.GetUserByID(controllers[i]->user_id));
|
||||
controllers[i]->user_id = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_RDOC_CAPTURE:
|
||||
VideoCore::TriggerCapture();
|
||||
break;
|
||||
@ -485,8 +293,10 @@ void WindowSDL::WaitEvent() {
|
||||
}
|
||||
|
||||
void WindowSDL::InitTimers() {
|
||||
SDL_AddTimer(4, &PollGyroAndAccel, controller);
|
||||
SDL_AddTimer(33, Input::MousePolling, (void*)controller);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
SDL_AddTimer(4, &PollController, controllers[i]);
|
||||
}
|
||||
SDL_AddTimer(33, Input::MousePolling, (void*)controllers[0]);
|
||||
}
|
||||
|
||||
void WindowSDL::RequestKeyboard() {
|
||||
@ -554,10 +364,44 @@ void WindowSDL::OnGamepadEvent(const SDL_Event* event) {
|
||||
// as it would break the entire touchpad handling
|
||||
// You can still bind other things to it though
|
||||
if (event->gbutton.button == SDL_GAMEPAD_BUTTON_TOUCHPAD) {
|
||||
controller->Button(0, OrbisPadButtonDataOffset::TouchPad, input_down);
|
||||
controllers[controllers.GetGamepadIndexFromJoystickId(event->gbutton.which)]->Button(
|
||||
OrbisPadButtonDataOffset::TouchPad, input_down);
|
||||
return;
|
||||
}
|
||||
|
||||
u8 gamepad;
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
|
||||
switch ((SDL_SensorType)event->gsensor.sensor) {
|
||||
case SDL_SENSOR_GYRO:
|
||||
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
|
||||
if (gamepad < 5) {
|
||||
controllers[gamepad]->UpdateGyro(event->gsensor.data);
|
||||
}
|
||||
break;
|
||||
case SDL_SENSOR_ACCEL:
|
||||
gamepad = controllers.GetGamepadIndexFromJoystickId(event->gsensor.which);
|
||||
if (gamepad < 5) {
|
||||
controllers[gamepad]->UpdateAcceleration(event->gsensor.data);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
|
||||
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
|
||||
controllers[controllers.GetGamepadIndexFromJoystickId(event->gtouchpad.which)]
|
||||
->SetTouchpadState(event->gtouchpad.finger,
|
||||
event->type != SDL_EVENT_GAMEPAD_TOUCHPAD_UP, event->gtouchpad.x,
|
||||
event->gtouchpad.y);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// add/remove it from the list
|
||||
bool inputs_changed = Input::UpdatePressedKeys(input_event);
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
@ -14,23 +14,8 @@ struct SDL_Gamepad;
|
||||
union SDL_Event;
|
||||
|
||||
namespace Input {
|
||||
|
||||
class SDLInputEngine : public Engine {
|
||||
public:
|
||||
~SDLInputEngine() override;
|
||||
void Init() override;
|
||||
void SetLightBarRGB(u8 r, u8 g, u8 b) override;
|
||||
void SetVibration(u8 smallMotor, u8 largeMotor) override;
|
||||
float GetGyroPollRate() const override;
|
||||
float GetAccelPollRate() const override;
|
||||
State ReadState() override;
|
||||
|
||||
private:
|
||||
float m_gyro_poll_rate = 0.0f;
|
||||
float m_accel_poll_rate = 0.0f;
|
||||
};
|
||||
|
||||
} // namespace Input
|
||||
class GameController;
|
||||
}
|
||||
|
||||
namespace Frontend {
|
||||
|
||||
@ -62,7 +47,7 @@ class WindowSDL {
|
||||
int keyboard_grab = 0;
|
||||
|
||||
public:
|
||||
explicit WindowSDL(s32 width, s32 height, Input::GameController* controller,
|
||||
explicit WindowSDL(s32 width, s32 height, Input::GameControllers* controllers,
|
||||
std::string_view window_title);
|
||||
~WindowSDL();
|
||||
|
||||
@ -100,7 +85,7 @@ private:
|
||||
private:
|
||||
s32 width;
|
||||
s32 height;
|
||||
Input::GameController* controller;
|
||||
Input::GameControllers controllers{};
|
||||
WindowSystemInfo window_info{};
|
||||
SDL_Window* window{};
|
||||
bool is_shown{};
|
||||
|
||||
@ -351,6 +351,15 @@ Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id ad
|
||||
&Sirit::Module::OpAtomicCompareExchange);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicFCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id cmp_value) {
|
||||
const auto u32_value = ctx.OpBitcast(ctx.U32[1], value);
|
||||
const auto u32_cmp = ctx.OpBitcast(ctx.U32[1], cmp_value);
|
||||
const auto result = BufferAtomicU32CmpSwap(ctx, inst, handle, address, u32_value, u32_cmp,
|
||||
&Sirit::Module::OpAtomicCompareExchange);
|
||||
return ctx.OpBitcast(ctx.F32[1], result);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value) {
|
||||
return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicIAdd);
|
||||
}
|
||||
|
||||
@ -108,6 +108,8 @@ Id EmitBufferAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addres
|
||||
Id EmitBufferAtomicSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id cmp_value);
|
||||
Id EmitBufferAtomicFCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id cmp_value);
|
||||
Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, u32 index);
|
||||
Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp);
|
||||
void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 comp);
|
||||
|
||||
@ -45,6 +45,14 @@ CopyShaderData ParseCopyShader(std::span<const u32> code) {
|
||||
sources[inst.dst[0].code] += inst.control.sopk.simm;
|
||||
break;
|
||||
}
|
||||
case Gcn::Opcode::S_BFM_B32: {
|
||||
ASSERT(inst.src[0].field == Gcn::OperandField::SignedConstIntPos &&
|
||||
inst.src[1].field == Gcn::OperandField::SignedConstIntPos);
|
||||
const auto src0 = inst.src[0].code - Gcn::OperandFieldRange::SignedConstIntPosMin + 1;
|
||||
const auto src1 = inst.src[1].code - Gcn::OperandFieldRange::SignedConstIntPosMin + 1;
|
||||
sources[inst.dst[0].code] = ((1 << src0) - 1) << src1;
|
||||
break;
|
||||
}
|
||||
case Gcn::Opcode::EXP: {
|
||||
const auto& exp = inst.control.exp;
|
||||
const IR::Attribute semantic = static_cast<IR::Attribute>(exp.target);
|
||||
@ -71,6 +79,7 @@ CopyShaderData ParseCopyShader(std::span<const u32> code) {
|
||||
ASSERT(sources[index] != -1);
|
||||
offsets[inst.src[1].code] += sources[index];
|
||||
}
|
||||
data.num_comps++;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@ -15,6 +15,7 @@ struct CopyShaderData {
|
||||
std::map<u32, std::pair<Shader::IR::Attribute, u32>> attr_map;
|
||||
u32 num_attrs{0};
|
||||
u32 output_vertices{0};
|
||||
u32 num_comps{0};
|
||||
};
|
||||
|
||||
CopyShaderData ParseCopyShader(std::span<const u32> code);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "common/assert.h"
|
||||
#include "core/libraries/kernel/process.h"
|
||||
#include "shader_recompiler/frontend/decode.h"
|
||||
|
||||
#include <magic_enum/magic_enum.hpp>
|
||||
@ -39,6 +40,7 @@ InstEncoding GetInstructionEncoding(u32 token) {
|
||||
encoding = static_cast<InstEncoding>(token & (u32)EncodingMask::MASK_6bit);
|
||||
switch (encoding) {
|
||||
case InstEncoding::VOP3:
|
||||
case InstEncoding::VOP3P:
|
||||
case InstEncoding::EXP:
|
||||
case InstEncoding::VINTRP:
|
||||
case InstEncoding::DS:
|
||||
@ -82,7 +84,6 @@ InstEncoding GetInstructionEncoding(u32 token) {
|
||||
break;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
return InstEncoding::ILLEGAL;
|
||||
}
|
||||
|
||||
@ -111,7 +112,7 @@ GcnInst GcnDecodeContext::decodeInstruction(GcnCodeSlice& code) {
|
||||
const uint32_t token = code.at(0);
|
||||
|
||||
InstEncoding encoding = GetInstructionEncoding(token);
|
||||
ASSERT_MSG(encoding != InstEncoding::ILLEGAL, "illegal encoding");
|
||||
ASSERT_MSG(encoding != InstEncoding::ILLEGAL, "illegal encoding: {:#x}", token);
|
||||
uint32_t encodingLen = getEncodingLength(encoding);
|
||||
|
||||
// Clear the instruction
|
||||
@ -155,6 +156,7 @@ uint32_t GcnDecodeContext::getEncodingLength(InstEncoding encoding) {
|
||||
break;
|
||||
|
||||
case InstEncoding::VOP3:
|
||||
case InstEncoding::VOP3P:
|
||||
case InstEncoding::MUBUF:
|
||||
case InstEncoding::MTBUF:
|
||||
case InstEncoding::MIMG:
|
||||
@ -189,6 +191,9 @@ uint32_t GcnDecodeContext::getOpMapOffset(InstEncoding encoding) {
|
||||
case InstEncoding::VOP3:
|
||||
offset = (uint32_t)OpcodeMap::OP_MAP_VOP3;
|
||||
break;
|
||||
case InstEncoding::VOP3P:
|
||||
offset = (uint32_t)OpcodeMap::OP_MAP_VOP3P;
|
||||
break;
|
||||
case InstEncoding::EXP:
|
||||
offset = (uint32_t)OpcodeMap::OP_MAP_EXP;
|
||||
break;
|
||||
@ -381,6 +386,9 @@ void GcnDecodeContext::decodeInstruction64(InstEncoding encoding, GcnCodeSlice&
|
||||
case InstEncoding::VOP3:
|
||||
decodeInstructionVOP3(hexInstruction);
|
||||
break;
|
||||
case InstEncoding::VOP3P:
|
||||
decodeInstructionVOP3P(hexInstruction);
|
||||
break;
|
||||
case InstEncoding::MUBUF:
|
||||
decodeInstructionMUBUF(hexInstruction);
|
||||
break;
|
||||
@ -712,6 +720,55 @@ void GcnDecodeContext::decodeInstructionVOP3(uint64_t hexInstruction) {
|
||||
}
|
||||
}
|
||||
|
||||
void GcnDecodeContext::decodeInstructionVOP3P(uint64_t hexInstruction) {
|
||||
u32 vdst = bit::extract(hexInstruction, 7, 0);
|
||||
u32 op = bit::extract(hexInstruction, 22, 16);
|
||||
u32 src0 = bit::extract(hexInstruction, 40, 32);
|
||||
u32 src1 = bit::extract(hexInstruction, 49, 41);
|
||||
u32 src2 = bit::extract(hexInstruction, 58, 50);
|
||||
|
||||
m_instruction.opcode = static_cast<Opcode>(op + static_cast<u32>(OpcodeMap::OP_MAP_VOP3P));
|
||||
|
||||
m_instruction.src[0].field = getOperandField(src0);
|
||||
m_instruction.src[0].code =
|
||||
m_instruction.src[0].field == OperandField::VectorGPR ? src0 - VectorGPRMin : src0;
|
||||
m_instruction.src[1].field = getOperandField(src1);
|
||||
m_instruction.src[1].code =
|
||||
m_instruction.src[1].field == OperandField::VectorGPR ? src1 - VectorGPRMin : src1;
|
||||
m_instruction.src[2].field = getOperandField(src2);
|
||||
m_instruction.src[2].code =
|
||||
m_instruction.src[2].field == OperandField::VectorGPR ? src2 - VectorGPRMin : src2;
|
||||
m_instruction.dst[0].field = OperandField::VectorGPR;
|
||||
m_instruction.dst[0].code = vdst;
|
||||
|
||||
m_instruction.control.vop3p = *reinterpret_cast<InstControlVOP3P*>(&hexInstruction);
|
||||
|
||||
// update input modifier
|
||||
auto& control = m_instruction.control.vop3p;
|
||||
for (u32 i = 0; i != 3; ++i) {
|
||||
if (control.neg & (1u << i)) {
|
||||
m_instruction.src[i].input_modifier.neg = true;
|
||||
}
|
||||
|
||||
if (control.neg_hi & (1u << i)) {
|
||||
m_instruction.src[i].input_modifier.neg_hi = true;
|
||||
}
|
||||
|
||||
if (control.op_sel & (1u << i)) {
|
||||
m_instruction.src[i].op_sel.op_sel = true;
|
||||
}
|
||||
|
||||
if (control.get_op_sel_hi(i)) {
|
||||
m_instruction.src[i].op_sel.op_sel_hi = true;
|
||||
}
|
||||
}
|
||||
|
||||
// update output modifier
|
||||
auto& outputMod = m_instruction.dst[0].output_modifier;
|
||||
|
||||
outputMod.clamp = static_cast<bool>(control.clamp);
|
||||
}
|
||||
|
||||
void GcnDecodeContext::decodeInstructionMUBUF(uint64_t hexInstruction) {
|
||||
u32 op = bit::extract(hexInstruction, 24, 18);
|
||||
u32 vaddr = bit::extract(hexInstruction, 39, 32);
|
||||
|
||||
@ -84,6 +84,7 @@ private:
|
||||
void decodeInstructionVINTRP(uint32_t hexInstruction);
|
||||
// 64 bits encodings
|
||||
void decodeInstructionVOP3(uint64_t hexInstruction);
|
||||
void decodeInstructionVOP3P(uint64_t hexInstruction);
|
||||
void decodeInstructionMUBUF(uint64_t hexInstruction);
|
||||
void decodeInstructionMTBUF(uint64_t hexInstruction);
|
||||
void decodeInstructionMIMG(uint64_t hexInstruction);
|
||||
|
||||
@ -1784,6 +1784,88 @@ constexpr std::array<InstFormat, 455> InstructionFormatVOP3 = {{
|
||||
{},
|
||||
}};
|
||||
|
||||
constexpr std::array<InstFormat, 35> InstructionFormatVOP3P = {{
|
||||
// 0 = V_PK_MAD_I16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 3, 1, ScalarType::Sint16,
|
||||
ScalarType::Sint16},
|
||||
// 1 = V_PK_MUL_LO_U16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 2 = V_PK_ADD_I16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Sint16,
|
||||
ScalarType::Sint16},
|
||||
// 3 = V_PK_SUB_I16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Sint16,
|
||||
ScalarType::Sint16},
|
||||
// 4 = V_PK_LSHLREV_B16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 5 = V_PK_LSHRREV_B16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 6 = V_PK_ASHRREV_I16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Sint16,
|
||||
ScalarType::Uint16},
|
||||
// 7 = V_PK_MAX_I16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Sint16,
|
||||
ScalarType::Sint16},
|
||||
// 8 = V_PK_MIN_I16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Sint16,
|
||||
ScalarType::Sint16},
|
||||
// 9 = V_PK_MAD_U16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 3, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 10 = V_PK_ADD_U16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 11 = V_PK_SUB_U16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 12 = V_PK_MAX_U16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 13 = V_PK_MIN_U16
|
||||
{InstClass::VectorIntArith16, InstCategory::VectorALU, 2, 1, ScalarType::Uint16,
|
||||
ScalarType::Uint16},
|
||||
// 14 = V_PK_FMA_F16
|
||||
{InstClass::VectorFpArith16, InstCategory::VectorALU, 3, 1, ScalarType::Float16,
|
||||
ScalarType::Float16},
|
||||
// 15 = V_PK_ADD_F16
|
||||
{InstClass::VectorFpArith16, InstCategory::VectorALU, 2, 1, ScalarType::Float16,
|
||||
ScalarType::Float16},
|
||||
// 16 = V_PK_MUL_F16
|
||||
{InstClass::VectorFpArith16, InstCategory::VectorALU, 2, 1, ScalarType::Float16,
|
||||
ScalarType::Float16},
|
||||
// 17 = V_PK_MIN_F16
|
||||
{InstClass::VectorFpArith16, InstCategory::VectorALU, 2, 1, ScalarType::Float16,
|
||||
ScalarType::Float16},
|
||||
// 18 = V_PK_MAX_F16
|
||||
{InstClass::VectorFpArith16, InstCategory::VectorALU, 2, 1, ScalarType::Float16,
|
||||
ScalarType::Float16},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
// 32 = V_MAD_MIX_F32
|
||||
{InstClass::VectorFpArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32,
|
||||
ScalarType::Float32},
|
||||
// 33 = V_MAD_MIXLO_F16
|
||||
{InstClass::VectorFpArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32,
|
||||
ScalarType::Float32},
|
||||
// 34 = V_MAD_MIXHI_F16
|
||||
{InstClass::VectorFpArith32, InstCategory::VectorALU, 3, 1, ScalarType::Float32,
|
||||
ScalarType::Float32},
|
||||
}};
|
||||
|
||||
constexpr std::array<InstFormat, 71> InstructionFormatVOP1 = {{
|
||||
// 0 = V_NOP
|
||||
{InstClass::VectorMisc, InstCategory::VectorALU, 0, 1, ScalarType::Any, ScalarType::Any},
|
||||
@ -3705,6 +3787,8 @@ InstFormat InstructionFormat(InstEncoding encoding, uint32_t opcode) {
|
||||
return InstructionFormatVOPC[opcode];
|
||||
case InstEncoding::VOP3:
|
||||
return InstructionFormatVOP3[opcode];
|
||||
case InstEncoding::VOP3P:
|
||||
return InstructionFormatVOP3P[opcode];
|
||||
case InstEncoding::EXP:
|
||||
return InstructionFormatEXP[opcode];
|
||||
case InstEncoding::VINTRP:
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "shader_recompiler/frontend/opcodes.h"
|
||||
|
||||
namespace Shader::Gcn {
|
||||
@ -25,6 +26,7 @@ enum OperandFieldRange {
|
||||
/// These are applied after loading an operand register.
|
||||
struct InputModifiers {
|
||||
bool neg = false;
|
||||
bool neg_hi = false;
|
||||
bool abs = false;
|
||||
};
|
||||
|
||||
@ -34,11 +36,18 @@ struct OutputModifiers {
|
||||
float multiplier = 0.f;
|
||||
};
|
||||
|
||||
struct OperandSelection {
|
||||
bool op_sel = false;
|
||||
bool op_sel_hi = false;
|
||||
};
|
||||
|
||||
struct InstOperand {
|
||||
OperandField field = OperandField::Undefined;
|
||||
ScalarType type = ScalarType::Undefined;
|
||||
InputModifiers input_modifier = {};
|
||||
OutputModifiers output_modifier = {};
|
||||
// only valid for packed 16bit operations
|
||||
OperandSelection op_sel = {};
|
||||
u32 code = 0xFFFFFFFF;
|
||||
};
|
||||
|
||||
@ -90,6 +99,32 @@ struct InstControlVOP3 {
|
||||
u64 neg : 3;
|
||||
};
|
||||
|
||||
struct InstControlVOP3P {
|
||||
u64 : 8;
|
||||
u64 neg_hi : 3;
|
||||
u64 op_sel : 3;
|
||||
u64 op_sel_hi_2 : 1;
|
||||
u64 clamp : 1;
|
||||
u64 : 43;
|
||||
u64 op_sel_hi_01 : 2;
|
||||
u64 neg : 3;
|
||||
|
||||
bool get_op_sel_hi(int idx) {
|
||||
switch (idx) {
|
||||
case 0:
|
||||
return (op_sel_hi_01 & 1) == 1;
|
||||
case 1:
|
||||
return ((op_sel_hi_01 >> 1) & 1) == 1;
|
||||
case 2:
|
||||
return (op_sel_hi_2 & 1) == 1;
|
||||
default:
|
||||
UNREACHABLE_MSG("get_op_sel_hi: {}", idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(InstControlVOP3P) == 8);
|
||||
|
||||
struct InstControlSMRD {
|
||||
u32 offset : 8;
|
||||
u32 imm : 1;
|
||||
@ -174,6 +209,7 @@ union InstControl {
|
||||
InstControlSOPK sopk;
|
||||
InstControlSOPP sopp;
|
||||
InstControlVOP3 vop3;
|
||||
InstControlVOP3P vop3p;
|
||||
InstControlSMRD smrd;
|
||||
InstControlMUBUF mubuf;
|
||||
InstControlMTBUF mtbuf;
|
||||
|
||||
@ -662,6 +662,33 @@ enum class OpcodeVOP3 : u32 {
|
||||
OP_RANGE_VOP3 = V_EXP_LEGACY_F32 + 1,
|
||||
};
|
||||
|
||||
enum class OpcodeVOP3P : u32 {
|
||||
V_PK_MAD_I16 = 0,
|
||||
V_PK_MUL_LO_U16 = 1,
|
||||
V_PK_ADD_I16 = 2,
|
||||
V_PK_SUB_I16 = 3,
|
||||
V_PK_LSHLREV_B16 = 4,
|
||||
V_PK_LSHRREV_B16 = 5,
|
||||
V_PK_ASHRREV_I16 = 6,
|
||||
V_PK_MAX_I16 = 7,
|
||||
V_PK_MIN_I16 = 8,
|
||||
V_PK_MAD_U16 = 9,
|
||||
V_PK_ADD_U16 = 10,
|
||||
V_PK_SUB_U16 = 11,
|
||||
V_PK_MAX_U16 = 12,
|
||||
V_PK_MIN_U16 = 13,
|
||||
V_PK_FMA_F16 = 14,
|
||||
V_PK_ADD_F16 = 15,
|
||||
V_PK_MUL_F16 = 16,
|
||||
V_PK_MIN_F16 = 17,
|
||||
V_PK_MAX_F16 = 18,
|
||||
V_MAD_MIX_F32 = 32,
|
||||
V_MAD_MIXLO_F16 = 33,
|
||||
V_MAD_MIXHI_F16 = 34,
|
||||
|
||||
OP_RANGE_VOP3P = V_MAD_MIXHI_F16 + 1,
|
||||
};
|
||||
|
||||
enum class OpcodeVOP1 : u32 {
|
||||
V_NOP = 0,
|
||||
V_MOV_B32 = 1,
|
||||
@ -1313,6 +1340,7 @@ enum class OpcodeMap : u32 {
|
||||
OP_MAP_MTBUF = OP_MAP_MUBUF + (u32)OpcodeMUBUF::OP_RANGE_MUBUF,
|
||||
OP_MAP_MIMG = OP_MAP_MTBUF + (u32)OpcodeMTBUF::OP_RANGE_MTBUF,
|
||||
OP_MAP_EXP = OP_MAP_MIMG + (u32)OpcodeMIMG::OP_RANGE_MIMG,
|
||||
OP_MAP_VOP3P = OP_MAP_EXP + (u32)OpcodeEXP::OP_RANGE_EXP,
|
||||
};
|
||||
|
||||
enum class Opcode : u32 {
|
||||
@ -2192,6 +2220,29 @@ enum class Opcode : u32 {
|
||||
IMAGE_SAMPLE_C_CD_CL_O = 111 + (u32)OpcodeMap::OP_MAP_MIMG,
|
||||
// EXP
|
||||
EXP = 0 + (u32)OpcodeMap::OP_MAP_EXP,
|
||||
// VOP3P
|
||||
V_PK_MAD_I16 = 0 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MUL_LO_U16 = 1 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_ADD_I16 = 2 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_SUB_I16 = 3 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_LSHLREV_B16 = 4 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_LSHRREV_B16 = 5 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_ASHRREV_I16 = 6 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MAX_I16 = 7 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MIN_I16 = 8 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MAD_U16 = 9 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_ADD_U16 = 10 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_SUB_U16 = 11 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MAX_U16 = 12 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MIN_U16 = 13 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_FMA_F16 = 14 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_ADD_F16 = 15 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MUL_F16 = 16 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MIN_F16 = 17 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_PK_MAX_F16 = 18 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_MAD_MIX_F32 = 32 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_MAD_MIXLO_F16 = 33 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
V_MAD_MIXHI_F16 = 34 + (u32)OpcodeMap::OP_MAP_VOP3P,
|
||||
};
|
||||
|
||||
enum class EncodingMask : u32 {
|
||||
@ -2222,6 +2273,8 @@ enum class InstEncoding : u32 {
|
||||
VOP3 = 0x00000034u << 26,
|
||||
/// bits [31:26] - (1 1 1 1 1 0)
|
||||
EXP = 0x0000003Eu << 26,
|
||||
/// bits [31:26] - (1 1 0 0 1 1)
|
||||
VOP3P = 0x00000033u << 26,
|
||||
/// bits [31:26] - (1 1 0 0 1 0)
|
||||
VINTRP = 0x00000032u << 26,
|
||||
/// bits [31:26] - (1 1 0 1 1 0)
|
||||
@ -2271,6 +2324,7 @@ enum class InstClass : u32 {
|
||||
VectorBitField32,
|
||||
VectorThreadMask,
|
||||
VectorBitField64,
|
||||
VectorFpArith16,
|
||||
VectorFpArith32,
|
||||
VectorFpRound32,
|
||||
VectorFpField32,
|
||||
@ -2281,6 +2335,7 @@ enum class InstClass : u32 {
|
||||
VectorFpField64,
|
||||
VectorFpTran64,
|
||||
VectorFpCmp64,
|
||||
VectorIntArith16,
|
||||
VectorIntArith32,
|
||||
VectorIntArith64,
|
||||
VectorIntCmp32,
|
||||
@ -2360,8 +2415,10 @@ enum class InstCategory : u32 {
|
||||
enum class ScalarType : u32 {
|
||||
Undefined,
|
||||
Any,
|
||||
Uint16,
|
||||
Uint32,
|
||||
Uint64,
|
||||
Sint16,
|
||||
Sint32,
|
||||
Sint64,
|
||||
Float16,
|
||||
|
||||
@ -116,6 +116,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
|
||||
return BUFFER_ATOMIC<IR::F32>(AtomicOp::Fmin, inst);
|
||||
case Opcode::BUFFER_ATOMIC_FMAX:
|
||||
return BUFFER_ATOMIC<IR::F32>(AtomicOp::Fmax, inst);
|
||||
case Opcode::BUFFER_ATOMIC_FCMPSWAP:
|
||||
return BUFFER_ATOMIC<IR::F32>(AtomicOp::FCmpSwap, inst);
|
||||
|
||||
// MIMG
|
||||
// Image load operations
|
||||
@ -379,6 +381,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
|
||||
const IR::Value cmp_val = ir.GetVectorReg(vdata + 1);
|
||||
return ir.BufferAtomicCmpSwap(handle, address, vdata_val, cmp_val, buffer_info);
|
||||
}
|
||||
case AtomicOp::FCmpSwap: {
|
||||
const IR::Value cmp_val = ir.GetVectorReg(vdata + 1);
|
||||
return ir.BufferAtomicFCmpSwap(handle, address, vdata_val, cmp_val, buffer_info);
|
||||
}
|
||||
case AtomicOp::Add:
|
||||
return ir.BufferAtomicIAdd(handle, address, vdata_val, buffer_info);
|
||||
case AtomicOp::Smin:
|
||||
|
||||
@ -627,6 +627,11 @@ Value IREmitter::BufferAtomicCmpSwap(const Value& handle, const Value& address,
|
||||
return Inst(Opcode::BufferAtomicCmpSwap32, Flags{info}, handle, address, vdata, cmp_value);
|
||||
}
|
||||
|
||||
Value IREmitter::BufferAtomicFCmpSwap(const Value& handle, const Value& address, const Value& vdata,
|
||||
const Value& cmp_value, BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicFCmpSwap32, Flags{info}, handle, address, vdata, cmp_value);
|
||||
}
|
||||
|
||||
U32 IREmitter::DataAppend(const U32& counter) {
|
||||
return Inst<U32>(Opcode::DataAppend, counter, Imm32(0));
|
||||
}
|
||||
|
||||
@ -166,6 +166,9 @@ public:
|
||||
[[nodiscard]] Value BufferAtomicCmpSwap(const Value& handle, const Value& address,
|
||||
const Value& value, const Value& cmp_value,
|
||||
BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicFCmpSwap(const Value& handle, const Value& address,
|
||||
const Value& value, const Value& cmp_value,
|
||||
BufferInstInfo info);
|
||||
|
||||
[[nodiscard]] U32 DataAppend(const U32& counter);
|
||||
[[nodiscard]] U32 DataConsume(const U32& counter);
|
||||
|
||||
@ -82,6 +82,7 @@ bool Inst::MayHaveSideEffects() const noexcept {
|
||||
case Opcode::BufferAtomicXor32:
|
||||
case Opcode::BufferAtomicSwap32:
|
||||
case Opcode::BufferAtomicCmpSwap32:
|
||||
case Opcode::BufferAtomicFCmpSwap32:
|
||||
case Opcode::DataAppend:
|
||||
case Opcode::DataConsume:
|
||||
case Opcode::WriteSharedU16:
|
||||
|
||||
@ -150,6 +150,7 @@ OPCODE(BufferAtomicOr32, U32, Opaq
|
||||
OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicCmpSwap32, U32, Opaque, Opaque, U32, U32, )
|
||||
OPCODE(BufferAtomicFCmpSwap32, F32, Opaque, Opaque, F32, F32, )
|
||||
|
||||
// Vector utility
|
||||
OPCODE(CompositeConstructU32x2, U32x2, U32, U32, )
|
||||
|
||||
@ -39,6 +39,7 @@ bool IsBufferAtomic(const IR::Inst& inst) {
|
||||
case IR::Opcode::BufferAtomicXor32:
|
||||
case IR::Opcode::BufferAtomicSwap32:
|
||||
case IR::Opcode::BufferAtomicCmpSwap32:
|
||||
case IR::Opcode::BufferAtomicFCmpSwap32:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@ -1088,7 +1089,8 @@ void PatchImageArgs(IR::Block& block, IR::Inst& inst, Info& info) {
|
||||
}
|
||||
|
||||
const auto image_handle = inst.Arg(0);
|
||||
const auto& image_res = info.images[image_handle.U32() & 0xFFFF];
|
||||
const auto binding_index = image_handle.U32() & 0xFFFF;
|
||||
const auto& image_res = info.images[binding_index];
|
||||
auto image = image_res.GetSharp(info);
|
||||
|
||||
// Sample instructions must be handled separately using address register data.
|
||||
@ -1097,7 +1099,7 @@ void PatchImageArgs(IR::Block& block, IR::Inst& inst, Info& info) {
|
||||
}
|
||||
|
||||
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
|
||||
const auto inst_info = inst.Flags<IR::TextureInstInfo>();
|
||||
auto inst_info = inst.Flags<IR::TextureInstInfo>();
|
||||
const auto view_type = image.GetViewType(image_res.is_array);
|
||||
|
||||
// Now that we know the image type, adjust texture coordinate vector.
|
||||
@ -1108,8 +1110,26 @@ void PatchImageArgs(IR::Block& block, IR::Inst& inst, Info& info) {
|
||||
return {body->Arg(0), body->Arg(1)};
|
||||
case AmdGpu::ImageType::Color1DArray: // x, slice, [lod]
|
||||
case AmdGpu::ImageType::Color2D: // x, y, [lod]
|
||||
case AmdGpu::ImageType::Color2DMsaa: // x, y. (sample is passed on different argument)
|
||||
return {ir.CompositeConstruct(body->Arg(0), body->Arg(1)), body->Arg(2)};
|
||||
case AmdGpu::ImageType::Color2DMsaa: // x, y. (sample is passed on different argument)
|
||||
{
|
||||
auto skip_lod = false;
|
||||
if (inst_info.has_lod) {
|
||||
const auto mipid = body->Arg(2);
|
||||
if (mipid.IsImmediate() && mipid.U32() == 0) {
|
||||
// if image_x_mip refers to a MSAA image, and mipid is 0, it is safe to be
|
||||
// skipped and fragid is taken from the next arg
|
||||
LOG_WARNING(Render_Recompiler, "Encountered a _mip instruction with MSAA "
|
||||
"image, and mipid is 0, skipping LoD");
|
||||
inst_info.has_lod.Assign(false);
|
||||
skip_lod = true;
|
||||
} else {
|
||||
UNREACHABLE_MSG(
|
||||
"Encountered a _mip instruction with MSAA image, and mipid is non-zero");
|
||||
}
|
||||
}
|
||||
return {ir.CompositeConstruct(body->Arg(0), body->Arg(1)), body->Arg(skip_lod ? 3 : 2)};
|
||||
}
|
||||
case AmdGpu::ImageType::Color2DArray: // x, y, slice, [lod]
|
||||
case AmdGpu::ImageType::Color2DMsaaArray: // x, y, slice. (sample is passed on different
|
||||
// argument)
|
||||
|
||||
@ -104,6 +104,13 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
|
||||
output_vertices, info.gs_copy_data.output_vertices);
|
||||
output_vertices = info.gs_copy_data.output_vertices;
|
||||
}
|
||||
u32 dwords_per_vertex = gs_info.out_vertex_data_size;
|
||||
if (info.gs_copy_data.num_comps && info.gs_copy_data.num_comps > dwords_per_vertex) {
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"VERT_ITEMSIZE {} is different than actual number of dwords per vertex {}",
|
||||
dwords_per_vertex, info.gs_copy_data.num_comps);
|
||||
dwords_per_vertex = info.gs_copy_data.num_comps;
|
||||
}
|
||||
|
||||
ForEachInstruction([&](IR::IREmitter& ir, IR::Inst& inst) {
|
||||
const auto opcode = inst.GetOpcode();
|
||||
@ -139,7 +146,7 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
|
||||
const auto offset = inst.Flags<IR::BufferInstInfo>().inst_offset.Value();
|
||||
const auto data = ir.BitCast<IR::F32>(IR::U32{inst.Arg(2)});
|
||||
const auto comp_ofs = output_vertices * 4u;
|
||||
const auto output_size = comp_ofs * gs_info.out_vertex_data_size;
|
||||
const auto output_size = comp_ofs * dwords_per_vertex;
|
||||
|
||||
const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u;
|
||||
const auto& it = info.gs_copy_data.attr_map.find(vc_read_ofs);
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/types.h"
|
||||
#include "imgui/renderer/imgui_core.h"
|
||||
#include "sdl_window.h"
|
||||
#include "video_core/renderer_vulkan/liverpool_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
@ -183,6 +184,7 @@ Instance::Instance(Frontend::WindowSDL& window, s32 physical_device_index,
|
||||
}
|
||||
|
||||
Instance::~Instance() {
|
||||
ImGui::Core::Shutdown(GetDevice());
|
||||
vmaDestroyAllocator(allocator);
|
||||
}
|
||||
|
||||
|
||||
@ -137,14 +137,20 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_
|
||||
|
||||
Presenter::~Presenter() {
|
||||
ImGui::Layer::RemoveLayer(Common::Singleton<Core::Devtools::Layer>::Instance());
|
||||
|
||||
draw_scheduler.Finish();
|
||||
present_scheduler.Finish();
|
||||
flip_scheduler.Finish();
|
||||
Check(draw_scheduler.CommandBuffer().reset());
|
||||
Check(present_scheduler.CommandBuffer().reset());
|
||||
Check(flip_scheduler.CommandBuffer().reset());
|
||||
|
||||
const vk::Device device = instance.GetDevice();
|
||||
for (auto& frame : present_frames) {
|
||||
vmaDestroyImage(instance.GetAllocator(), frame.image, frame.allocation);
|
||||
device.destroyImageView(frame.image_view);
|
||||
device.destroyFence(frame.present_done);
|
||||
}
|
||||
ImGui::Core::Shutdown(device);
|
||||
}
|
||||
|
||||
bool Presenter::IsVideoOutSurface(const AmdGpu::ColorBuffer& color_buffer) const {
|
||||
|
||||
@ -112,13 +112,13 @@ private:
|
||||
u32 expected_frame_width{1920};
|
||||
u32 expected_frame_height{1080};
|
||||
|
||||
Frontend::WindowSDL& window;
|
||||
Instance instance;
|
||||
HostPasses::FsrPass fsr_pass;
|
||||
HostPasses::FsrPass::Settings fsr_settings{};
|
||||
HostPasses::PostProcessingPass::Settings pp_settings{};
|
||||
HostPasses::PostProcessingPass pp_pass;
|
||||
Frontend::WindowSDL& window;
|
||||
AmdGpu::Liverpool* liverpool;
|
||||
Instance instance;
|
||||
Scheduler draw_scheduler;
|
||||
Scheduler present_scheduler;
|
||||
Scheduler flip_scheduler;
|
||||
|
||||
@ -261,6 +261,12 @@ void Swapchain::Destroy() {
|
||||
LOG_WARNING(Render_Vulkan, "Failed to wait for device to become idle: {}",
|
||||
vk::to_string(wait_result));
|
||||
}
|
||||
|
||||
for (auto& image_view : images_view) {
|
||||
device.destroyImageView(image_view);
|
||||
}
|
||||
images_view.clear();
|
||||
|
||||
if (swapchain) {
|
||||
device.destroySwapchainKHR(swapchain);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user