Merge branch 'main' into m4aac

This commit is contained in:
georgemoralis 2025-11-19 14:52:38 +02:00 committed by GitHub
commit 9278381bb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
61 changed files with 4398 additions and 524 deletions

6
.gitmodules vendored
View File

@ -108,8 +108,12 @@
branch = dist
[submodule "externals/MoltenVK"]
path = externals/MoltenVK
url = https://github.com/KhronosGroup/MoltenVK.git
url = https://github.com/shadPS4-emu/ext-MoltenVK.git
shallow = true
[submodule "externals/json"]
path = externals/json
url = https://github.com/nlohmann/json.git
[submodule "externals/sdl3_mixer"]
path = externals/sdl3_mixer
url = https://github.com/libsdl-org/SDL_mixer
shallow = true

View File

@ -203,7 +203,7 @@ execute_process(
# Set Version
set(EMULATOR_VERSION_MAJOR "0")
set(EMULATOR_VERSION_MINOR "12")
set(EMULATOR_VERSION_PATCH "1")
set(EMULATOR_VERSION_PATCH "6")
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
@ -229,6 +229,7 @@ find_package(magic_enum 0.9.7 CONFIG)
find_package(PNG 1.6 MODULE)
find_package(RenderDoc 1.6.0 MODULE)
find_package(SDL3 3.1.2 CONFIG)
find_package(SDL3_mixer 2.8.1 CONFIG)
find_package(stb MODULE)
find_package(toml11 4.2.0 CONFIG)
find_package(tsl-robin-map 1.3.0 CONFIG)
@ -463,6 +464,12 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp
src/core/libraries/mouse/mouse.h
src/core/libraries/web_browser_dialog/webbrowserdialog.cpp
src/core/libraries/web_browser_dialog/webbrowserdialog.h
src/core/libraries/font/font.cpp
src/core/libraries/font/font.h
src/core/libraries/font/fontft.cpp
src/core/libraries/font/fontft.h
src/core/libraries/font/font_error.h
)
set(VIDEOOUT_LIB src/core/libraries/videoout/buffer.h
@ -535,6 +542,10 @@ set(RANDOM_LIB src/core/libraries/random/random.cpp
set(USBD_LIB src/core/libraries/usbd/usbd.cpp
src/core/libraries/usbd/usbd.h
src/core/libraries/usbd/usb_backend.h
src/core/libraries/usbd/emulated/dimensions.cpp
src/core/libraries/usbd/emulated/dimensions.h
src/core/libraries/usbd/emulated/infinity.cpp
src/core/libraries/usbd/emulated/infinity.h
src/core/libraries/usbd/emulated/skylander.cpp
src/core/libraries/usbd/emulated/skylander.h
)
@ -951,6 +962,8 @@ set(VIDEO_CORE src/video_core/amdgpu/cb_db_extent.h
src/video_core/buffer_cache/buffer.h
src/video_core/buffer_cache/buffer_cache.cpp
src/video_core/buffer_cache/buffer_cache.h
src/video_core/buffer_cache/fault_manager.cpp
src/video_core/buffer_cache/fault_manager.h
src/video_core/buffer_cache/memory_tracker.h
src/video_core/buffer_cache/range_set.h
src/video_core/buffer_cache/region_definitions.h
@ -1062,7 +1075,7 @@ add_executable(shadps4
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 pugixml::pugixml stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json)
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 stb::headers libusb::usb lfreist-hwinfo::hwinfo nlohmann_json::nlohmann_json)
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")

View File

@ -37,6 +37,9 @@
<category translate="no">Game</category>
</categories>
<releases>
<release version="0.12.5" date="2025-11-07">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5</url>
</release>
<release version="0.12.0" date="2025-10-31">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.0</url>
</release>

View File

@ -36,16 +36,11 @@ Go through the Git for Windows installation as normal
1. Open up Visual Studio, select `Open a local folder` and select the folder with the shadPS4 source code. The folder should contain `CMakeLists.txt`
2. Change Clang x64 Debug to Clang x64 Release if you want a regular, non-debug build.
3. If you want to build shadPS4 with the Qt Gui, simply select Clang x64 Release with Qt instead.
4. Change the project to build to shadps4.exe
5. Build -> Build All
3. Change the project to build to shadps4.exe
4. Build -> Build All
Your shadps4.exe will be in `C:\path\to\source\Build\x64-Clang-Release\`
To automatically populate the necessary files to run shadPS4.exe, run in a command prompt or terminal:
`C:\Qt\<QtVersion>\msvc2022_64\bin\windeployqt6.exe "C:\path\to\shadps4.exe"`
(Change Qt path if you've installed it to non-default path)
## Option 2: MSYS2/MinGW
> [!IMPORTANT]
@ -73,7 +68,6 @@ ARM64-based computers, follow:
1. Open "MSYS2 CLANGARM64" from your new applications
2. Run `pacman -Syu`, let it complete;
3. Run `pacman -S --needed git mingw-w64-clang-aarch64-binutils mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-rapidjson mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-ffmpeg`
1. Optional (Qt only): run `pacman -S --needed mingw-w64-clang-aarch64-qt6-base mingw-w64-clang-aarch64-qt6-tools mingw-w64-clang-aarch64-qt6-multimedia`
4. Run `git clone --depth 1 --recursive https://github.com/shadps4-emu/shadPS4`
5. Run `cd shadPS4`
6. Run `cmake -S . -B build -DCMAKE_C_COMPILER="clang.exe" -DCMAKE_CXX_COMPILER="clang++.exe" -DCMAKE_CXX_FLAGS="-O2 -march=native"`

View File

@ -63,6 +63,18 @@ if (NOT TARGET SDL3::SDL3)
add_subdirectory(sdl3)
endif()
# SDL3_mixer
if (NOT TARGET SDL3_mixer::SDL3_mixer)
set(SDLMIXER_FLAC OFF)
set(SDLMIXER_OGG OFF)
set(SDLMIXER_MOD OFF)
set(SDLMIXER_MIDI OFF)
set(SDLMIXER_OPUS OFF)
set(SDLMIXER_WAVPACK OFF)
set(BUILD_SHARED_LIBS OFF)
add_subdirectory(sdl3_mixer)
endif()
# vulkan-headers
if (NOT TARGET Vulkan::Headers)
set(VULKAN_HEADERS_ENABLE_MODULE OFF)

2
externals/MoltenVK vendored

@ -1 +1 @@
Subproject commit b23d42534622cd9926fe526fec1b7f8795a2853c
Subproject commit f168dec05998ab0ca09a400bab6831a95c0bdb2e

1
externals/sdl3_mixer vendored Submodule

@ -0,0 +1 @@
Subproject commit 4182794ea45fe28568728670c6f1583855d0e85c

View File

@ -6,42 +6,43 @@ with import (fetchTarball "https://github.com/nixos/nixpkgs/archive/cfd19cdc5468
pkgs.mkShell {
name = "shadps4-build-env";
nativeBuildInputs = [
pkgs.llvmPackages_18.clang
pkgs.cmake
pkgs.pkg-config
pkgs.git
nativeBuildInputs = with pkgs; [
llvmPackages_18.clang
cmake
pkg-config
git
util-linux
];
buildInputs = [
pkgs.alsa-lib
pkgs.libpulseaudio
pkgs.openal
pkgs.zlib
pkgs.libedit
pkgs.udev
pkgs.libevdev
pkgs.SDL2
pkgs.jack2
pkgs.sndio
buildInputs = with pkgs; [
alsa-lib
libpulseaudio
openal
zlib
libedit
udev
libevdev
SDL2
jack2
sndio
pkgs.vulkan-headers
pkgs.vulkan-utility-libraries
pkgs.vulkan-tools
vulkan-headers
vulkan-utility-libraries
vulkan-tools
pkgs.ffmpeg
pkgs.fmt
pkgs.glslang
pkgs.libxkbcommon
pkgs.wayland
pkgs.xorg.libxcb
pkgs.xorg.xcbutil
pkgs.xorg.xcbutilkeysyms
pkgs.xorg.xcbutilwm
pkgs.sdl3
pkgs.stb
pkgs.wayland-protocols
pkgs.libpng
ffmpeg
fmt
glslang
libxkbcommon
wayland
xorg.libxcb
xorg.xcbutil
xorg.xcbutilkeysyms
xorg.xcbutilwm
sdl3
stb
wayland-protocols
libpng
];
shellHook = ''

View File

@ -68,6 +68,7 @@ class ElfInfo {
std::string app_ver{};
u32 firmware_ver = 0;
u32 raw_firmware_ver = 0;
u32 sdk_ver = 0;
PSFAttributes psf_attributes{};
std::filesystem::path splash_path{};
@ -117,6 +118,11 @@ public:
return raw_firmware_ver;
}
[[nodiscard]] u32 CompiledSdkVer() const {
ASSERT(initialized);
return sdk_ver;
}
[[nodiscard]] const PSFAttributes& GetPSFAttributes() const {
ASSERT(initialized);
return psf_attributes;

View File

@ -40,28 +40,30 @@ namespace {
switch (mode) {
case FileAccessMode::Read:
return L"rb";
case FileAccessMode::Write:
return L"wb";
case FileAccessMode::Append:
return L"ab";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return L"r+b";
case FileAccessMode::ReadAppend:
return L"a+b";
case FileAccessMode::Create:
return L"wb";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return L"r";
case FileAccessMode::Write:
return L"w";
case FileAccessMode::Append:
return L"a";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return L"r+";
case FileAccessMode::ReadAppend:
return L"a+";
case FileAccessMode::Create:
return L"w";
}
break;
}
@ -91,28 +93,30 @@ namespace {
switch (mode) {
case FileAccessMode::Read:
return "rb";
case FileAccessMode::Write:
return "wb";
case FileAccessMode::Append:
return "ab";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return "r+b";
case FileAccessMode::ReadAppend:
return "a+b";
case FileAccessMode::Create:
return "wb";
}
break;
case FileType::TextFile:
switch (mode) {
case FileAccessMode::Read:
return "r";
case FileAccessMode::Write:
return "w";
case FileAccessMode::Append:
return "a";
case FileAccessMode::Write:
case FileAccessMode::ReadWrite:
return "r+";
case FileAccessMode::ReadAppend:
return "a+";
case FileAccessMode::Create:
return "w";
}
break;
}

View File

@ -21,9 +21,8 @@ enum class FileAccessMode {
*/
Read = 1 << 0,
/**
* If the file at path exists, the existing contents of the file are erased.
* The empty file is then opened for writing.
* If the file at path does not exist, it creates and opens a new empty file for writing.
* If the file at path exists, it opens the file for writing.
* If the file at path does not exist, it fails to open the file.
*/
Write = 1 << 1,
/**
@ -42,6 +41,12 @@ enum class FileAccessMode {
* reading and appending.
*/
ReadAppend = Read | Append,
/**
* If the file at path exists, the existing contents of the file are erased.
* The empty file is then opened for writing.
* If the file at path does not exist, it creates and opens a new empty file for writing.
*/
Create = 1 << 3,
};
DECLARE_ENUM_FLAG_OPERATORS(FileAccessMode);
@ -102,6 +107,11 @@ public:
return file != nullptr;
}
bool IsWriteOnly() const {
return file_access_mode == FileAccessMode::Append ||
file_access_mode == FileAccessMode::Write;
}
uintptr_t GetFileMapping();
int Open(const std::filesystem::path& path, FileAccessMode mode,
@ -210,7 +220,7 @@ public:
}
static size_t WriteBytes(const std::filesystem::path path, const auto& data) {
IOFile out(path, FileAccessMode::Write);
IOFile out(path, FileAccessMode::Create);
return out.Write(data);
}
std::FILE* file = nullptr;

View File

@ -62,7 +62,7 @@ private:
class FileBackend {
public:
explicit FileBackend(const std::filesystem::path& filename, bool should_append = false)
: file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Write,
: file{filename, should_append ? FS::FileAccessMode::Append : FS::FileAccessMode::Create,
FS::FileType::TextFile} {}
~FileBackend() = default;
@ -182,7 +182,13 @@ public:
}
void PushEntry(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, std::string message) {
const char* function, const char* format, const fmt::format_args& args) {
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
return;
}
const auto message = fmt::vformat(format, args);
// Propagate important log messages to the profiler
if (IsProfilerConnected()) {
const auto& msg_str = fmt::format("[{}] {}", GetLogClassName(log_class), message);
@ -201,10 +207,6 @@ public:
}
}
if (!filter.CheckMessage(log_class, log_level) || !Config::getLoggingEnabled()) {
return;
}
using std::chrono::duration_cast;
using std::chrono::microseconds;
using std::chrono::steady_clock;
@ -324,8 +326,8 @@ void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args) {
if (!initialization_in_progress_suppress_logging) [[likely]] {
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
fmt::vformat(format, args));
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function, format,
args);
}
}
} // namespace Common::Log

View File

@ -140,6 +140,8 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, NpParty) \
SUB(Lib, Zlib) \
SUB(Lib, Hmd) \
SUB(Lib, Font) \
SUB(Lib, FontFt) \
SUB(Lib, HmdSetupDialog) \
SUB(Lib, SigninDialog) \
SUB(Lib, Camera) \

View File

@ -114,6 +114,8 @@ enum class Class : u8 {
Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation.
Lib_CompanionUtil, ///< The LibCompanionUtil implementation.
Lib_VrTracker, ///< The LibSceVrTracker implementation.
Lib_Font, ///< The libSceFont implementation.
Lib_FontFt, ///< The libSceFontFt implementation.
Frontend, ///< Emulator UI
Render, ///< Video Core
Render_Vulkan, ///< Vulkan backend

View File

@ -6,6 +6,7 @@
#include "common/arch.h"
#include "common/assert.h"
#include "common/config.h"
#include "common/elf_info.h"
#include "common/error.h"
#include "core/address_space.h"
#include "core/libraries/kernel/memory.h"
@ -103,8 +104,8 @@ struct AddressSpace::Impl {
GetSystemInfo(&sys_info);
u64 alignment = sys_info.dwAllocationGranularity;
// Determine the host OS build number
// Retrieve module handle for ntdll
// Older Windows builds have a severe performance issue with VirtualAlloc2.
// We need to get the host's Windows version, then determine if it needs a workaround.
auto ntdll_handle = GetModuleHandleW(L"ntdll.dll");
ASSERT_MSG(ntdll_handle, "Failed to retrieve ntdll handle");
@ -120,12 +121,20 @@ struct AddressSpace::Impl {
u64 supported_user_max = USER_MAX;
// This is the build number for Windows 11 22H2
static constexpr s32 AffectedBuildNumber = 22621;
if (os_version_info.dwBuildNumber <= AffectedBuildNumber) {
// Older Windows builds have an issue with VirtualAlloc2 on higher addresses.
// To prevent regressions, limit the maximum address we reserve for this platform.
supported_user_max = 0x11000000000ULL;
LOG_WARNING(Core, "Windows 10 detected, reducing user max to {:#x} to avoid problems",
supported_user_max);
// Higher PS4 firmware versions prevent higher address mappings too.
s32 sdk_ver = Common::ElfInfo::Instance().CompiledSdkVer();
if (os_version_info.dwBuildNumber <= AffectedBuildNumber ||
sdk_ver >= Common::ElfInfo::FW_30) {
supported_user_max = 0x10000000000ULL;
// Only log the message if we're restricting the user max due to operating system.
// Since higher compiled SDK versions also get reduced max, we don't need to log there.
if (sdk_ver < Common::ElfInfo::FW_30) {
LOG_WARNING(
Core,
"Older Windows version detected, reducing user max to {:#x} to avoid problems",
supported_user_max);
}
}
// Determine the free address ranges we can access.

View File

@ -152,7 +152,7 @@ inline std::string RunDisassembler(const std::string& disassembler_cli, const T&
}
} else {
cli.replace(pos, src_arg.size(), "\"" + bin_path.string() + "\"");
Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Write);
Common::FS::IOFile file(bin_path, Common::FS::FileAccessMode::Create);
file.Write(shader_code);
file.Close();

View File

@ -123,7 +123,7 @@ void FrameDumpViewer::Draw() {
const auto fname = fmt::format("{:%F %H-%M-%S} {}_{}_{}.bin", now_time,
magic_enum::enum_name(selected_queue_type),
selected_submit_num, selected_queue_num2);
Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Write);
Common::FS::IOFile file(fname, Common::FS::FileAccessMode::Create);
const auto& data = frame_dump->queues[selected_cmd].data;
if (file.IsOpen()) {
DebugState.ShowDebugMessage(fmt::format("Dumping cmd as {}", fname));

View File

@ -99,7 +99,7 @@ bool PSF::Open(const std::vector<u8>& psf_buffer) {
}
bool PSF::Encode(const std::filesystem::path& filepath) const {
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Write);
Common::FS::IOFile file(filepath, Common::FS::FileAccessMode::Create);
if (!file.IsOpen()) {
return false;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Font {
struct OrbisFontTextCharacter {
// Other fields...
struct OrbisFontTextCharacter* next; // Pointer to the next node 0x00
struct OrbisFontTextCharacter* prev; // Pointer to the next node 0x08
void* textOrder; // Field at offset 0x10 (pointer to text order info)
u32 characterCode; // Field assumed at offset 0x28
u8 unkn_0x31; // Offset 0x31
u8 unkn_0x33; // Offset 0x33
u8 charType; // Field assumed at offset 0x39
u8 bidiLevel; // Field assumed at offset 0x3B stores the Bidi level
u8 formatFlags; // Field at offset 0x3D (stores format-related flags)
};
struct OrbisFontRenderSurface {
void* buffer;
s32 widthByte;
s8 pixelSizeByte;
u8 unkn_0xd;
u8 styleFlag;
u8 unkn_0xf;
s32 width, height;
u32 sc_x0;
u32 sc_y0;
u32 sc_x1;
u32 sc_y1;
void* unkn_28[3];
};
struct OrbisFontStyleFrame {
/*0x00*/ u16 magic; // Expected to be 0xF09
/*0x02*/ u16 flags;
/*0x04*/ s32 dpiX; // DPI scaling factor for width
/*0x08*/ s32 dpiY; // DPI scaling factor for height
/*0x0c*/ s32 scalingFlag; // Indicates whether scaling is enabled
/*0x10*/
/*0x14*/ float scaleWidth; // Width scaling factor
/*0x18*/ float scaleHeight; // Height scaling factor
/*0x1c*/ float weightXScale;
/*0x20*/ float weightYScale;
/*0x24*/ float slantRatio;
};
s32 PS4_SYSV_ABI sceFontAttachDeviceCacheBuffer();
s32 PS4_SYSV_ABI sceFontBindRenderer();
s32 PS4_SYSV_ABI sceFontCharacterGetBidiLevel(OrbisFontTextCharacter* textCharacter,
int* bidiLevel);
s32 PS4_SYSV_ABI sceFontCharacterGetSyllableStringState();
s32 PS4_SYSV_ABI sceFontCharacterGetTextFontCode();
s32 PS4_SYSV_ABI sceFontCharacterGetTextOrder(OrbisFontTextCharacter* textCharacter,
void** pTextOrder);
u32 PS4_SYSV_ABI sceFontCharacterLooksFormatCharacters(OrbisFontTextCharacter* textCharacter);
u32 PS4_SYSV_ABI sceFontCharacterLooksWhiteSpace(OrbisFontTextCharacter* textCharacter);
OrbisFontTextCharacter* PS4_SYSV_ABI
sceFontCharacterRefersTextBack(OrbisFontTextCharacter* textCharacter);
OrbisFontTextCharacter* PS4_SYSV_ABI
sceFontCharacterRefersTextNext(OrbisFontTextCharacter* textCharacter);
s32 PS4_SYSV_ABI sceFontCharactersRefersTextCodes();
s32 PS4_SYSV_ABI sceFontClearDeviceCache();
s32 PS4_SYSV_ABI sceFontCloseFont();
s32 PS4_SYSV_ABI sceFontControl();
s32 PS4_SYSV_ABI sceFontCreateGraphicsDevice();
s32 PS4_SYSV_ABI sceFontCreateGraphicsService();
s32 PS4_SYSV_ABI sceFontCreateGraphicsServiceWithEdition();
s32 PS4_SYSV_ABI sceFontCreateLibrary();
s32 PS4_SYSV_ABI sceFontCreateLibraryWithEdition();
s32 PS4_SYSV_ABI sceFontCreateRenderer();
s32 PS4_SYSV_ABI sceFontCreateRendererWithEdition();
s32 PS4_SYSV_ABI sceFontCreateString();
s32 PS4_SYSV_ABI sceFontCreateWords();
s32 PS4_SYSV_ABI sceFontCreateWritingLine();
s32 PS4_SYSV_ABI sceFontDefineAttribute();
s32 PS4_SYSV_ABI sceFontDeleteGlyph();
s32 PS4_SYSV_ABI sceFontDestroyGraphicsDevice();
s32 PS4_SYSV_ABI sceFontDestroyGraphicsService();
s32 PS4_SYSV_ABI sceFontDestroyLibrary();
s32 PS4_SYSV_ABI sceFontDestroyRenderer();
s32 PS4_SYSV_ABI sceFontDestroyString();
s32 PS4_SYSV_ABI sceFontDestroyWords();
s32 PS4_SYSV_ABI sceFontDestroyWritingLine();
s32 PS4_SYSV_ABI sceFontDettachDeviceCacheBuffer();
s32 PS4_SYSV_ABI sceFontGenerateCharGlyph();
s32 PS4_SYSV_ABI sceFontGetAttribute();
s32 PS4_SYSV_ABI sceFontGetCharGlyphCode();
s32 PS4_SYSV_ABI sceFontGetCharGlyphMetrics();
s32 PS4_SYSV_ABI sceFontGetEffectSlant();
s32 PS4_SYSV_ABI sceFontGetEffectWeight();
s32 PS4_SYSV_ABI sceFontGetFontGlyphsCount();
s32 PS4_SYSV_ABI sceFontGetFontGlyphsOutlineProfile();
s32 PS4_SYSV_ABI sceFontGetFontMetrics();
s32 PS4_SYSV_ABI sceFontGetFontResolution();
s32 PS4_SYSV_ABI sceFontGetFontStyleInformation();
s32 PS4_SYSV_ABI sceFontGetGlyphExpandBufferState();
s32 PS4_SYSV_ABI sceFontGetHorizontalLayout();
s32 PS4_SYSV_ABI sceFontGetKerning();
s32 PS4_SYSV_ABI sceFontGetLibrary();
s32 PS4_SYSV_ABI sceFontGetPixelResolution();
s32 PS4_SYSV_ABI sceFontGetRenderCharGlyphMetrics();
s32 PS4_SYSV_ABI sceFontGetRenderEffectSlant();
s32 PS4_SYSV_ABI sceFontGetRenderEffectWeight();
s32 PS4_SYSV_ABI sceFontGetRenderScaledKerning();
s32 PS4_SYSV_ABI sceFontGetRenderScalePixel();
s32 PS4_SYSV_ABI sceFontGetRenderScalePoint();
s32 PS4_SYSV_ABI sceFontGetResolutionDpi();
s32 PS4_SYSV_ABI sceFontGetScalePixel();
s32 PS4_SYSV_ABI sceFontGetScalePoint();
s32 PS4_SYSV_ABI sceFontGetScriptLanguage();
s32 PS4_SYSV_ABI sceFontGetTypographicDesign();
s32 PS4_SYSV_ABI sceFontGetVerticalLayout();
s32 PS4_SYSV_ABI sceFontGlyphDefineAttribute();
s32 PS4_SYSV_ABI sceFontGlyphGetAttribute();
s32 PS4_SYSV_ABI sceFontGlyphGetGlyphForm();
s32 PS4_SYSV_ABI sceFontGlyphGetMetricsForm();
s32 PS4_SYSV_ABI sceFontGlyphGetScalePixel();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetrics();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontal();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalAdvance();
s32 PS4_SYSV_ABI sceFontGlyphRefersMetricsHorizontalX();
s32 PS4_SYSV_ABI sceFontGlyphRefersOutline();
s32 PS4_SYSV_ABI sceFontGlyphRenderImage();
s32 PS4_SYSV_ABI sceFontGlyphRenderImageHorizontal();
s32 PS4_SYSV_ABI sceFontGlyphRenderImageVertical();
s32 PS4_SYSV_ABI sceFontGraphicsBeginFrame();
s32 PS4_SYSV_ABI sceFontGraphicsDrawingCancel();
s32 PS4_SYSV_ABI sceFontGraphicsDrawingFinish();
s32 PS4_SYSV_ABI sceFontGraphicsEndFrame();
s32 PS4_SYSV_ABI sceFontGraphicsExchangeResource();
s32 PS4_SYSV_ABI sceFontGraphicsFillMethodInit();
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotInit();
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetLayout();
s32 PS4_SYSV_ABI sceFontGraphicsFillPlotSetMapping();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesInit();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetFillEffect();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetLayout();
s32 PS4_SYSV_ABI sceFontGraphicsFillRatesSetMapping();
s32 PS4_SYSV_ABI sceFontGraphicsGetDeviceUsage();
s32 PS4_SYSV_ABI sceFontGraphicsRegionInit();
s32 PS4_SYSV_ABI sceFontGraphicsRegionInitCircular();
s32 PS4_SYSV_ABI sceFontGraphicsRegionInitRoundish();
s32 PS4_SYSV_ABI sceFontGraphicsRelease();
s32 PS4_SYSV_ABI sceFontGraphicsRenderResource();
s32 PS4_SYSV_ABI sceFontGraphicsSetFramePolicy();
s32 PS4_SYSV_ABI sceFontGraphicsSetupClipping();
s32 PS4_SYSV_ABI sceFontGraphicsSetupColorRates();
s32 PS4_SYSV_ABI sceFontGraphicsSetupFillMethod();
s32 PS4_SYSV_ABI sceFontGraphicsSetupFillRates();
s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFill();
s32 PS4_SYSV_ABI sceFontGraphicsSetupGlyphFillPlot();
s32 PS4_SYSV_ABI sceFontGraphicsSetupHandleDefault();
s32 PS4_SYSV_ABI sceFontGraphicsSetupLocation();
s32 PS4_SYSV_ABI sceFontGraphicsSetupPositioning();
s32 PS4_SYSV_ABI sceFontGraphicsSetupRotation();
s32 PS4_SYSV_ABI sceFontGraphicsSetupScaling();
s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFill();
s32 PS4_SYSV_ABI sceFontGraphicsSetupShapeFillPlot();
s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvas();
s32 PS4_SYSV_ABI sceFontGraphicsStructureCanvasSequence();
s32 PS4_SYSV_ABI sceFontGraphicsStructureDesign();
s32 PS4_SYSV_ABI sceFontGraphicsStructureDesignResource();
s32 PS4_SYSV_ABI sceFontGraphicsStructureSurfaceTexture();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateClipping();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateColorRates();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillMethod();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateFillRates();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFill();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateGlyphFillPlot();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateLocation();
s32 PS4_SYSV_ABI sceFontGraphicsUpdatePositioning();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateRotation();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateScaling();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFill();
s32 PS4_SYSV_ABI sceFontGraphicsUpdateShapeFillPlot();
s32 PS4_SYSV_ABI sceFontMemoryInit();
s32 PS4_SYSV_ABI sceFontMemoryTerm();
s32 PS4_SYSV_ABI sceFontOpenFontFile();
s32 PS4_SYSV_ABI sceFontOpenFontInstance();
s32 PS4_SYSV_ABI sceFontOpenFontMemory();
s32 PS4_SYSV_ABI sceFontOpenFontSet();
s32 PS4_SYSV_ABI sceFontRebindRenderer();
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImage();
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageHorizontal();
s32 PS4_SYSV_ABI sceFontRenderCharGlyphImageVertical();
s32 PS4_SYSV_ABI sceFontRendererGetOutlineBufferSize();
s32 PS4_SYSV_ABI sceFontRendererResetOutlineBuffer();
s32 PS4_SYSV_ABI sceFontRendererSetOutlineBufferPolicy();
void PS4_SYSV_ABI sceFontRenderSurfaceInit(OrbisFontRenderSurface* renderSurface, void* buffer,
int bufWidthByte, int pixelSizeByte, int widthPixel,
int heightPixel);
void PS4_SYSV_ABI sceFontRenderSurfaceSetScissor(OrbisFontRenderSurface* renderSurface, int x0,
int y0, int w, int h);
s32 PS4_SYSV_ABI sceFontRenderSurfaceSetStyleFrame(OrbisFontRenderSurface* renderSurface,
OrbisFontStyleFrame* styleFrame);
s32 PS4_SYSV_ABI sceFontSetEffectSlant();
s32 PS4_SYSV_ABI sceFontSetEffectWeight();
s32 PS4_SYSV_ABI sceFontSetFontsOpenMode();
s32 PS4_SYSV_ABI sceFontSetResolutionDpi();
s32 PS4_SYSV_ABI sceFontSetScalePixel();
s32 PS4_SYSV_ABI sceFontSetScalePoint();
s32 PS4_SYSV_ABI sceFontSetScriptLanguage();
s32 PS4_SYSV_ABI sceFontSetTypographicDesign();
s32 PS4_SYSV_ABI sceFontSetupRenderEffectSlant();
s32 PS4_SYSV_ABI sceFontSetupRenderEffectWeight();
s32 PS4_SYSV_ABI sceFontSetupRenderScalePixel();
s32 PS4_SYSV_ABI sceFontSetupRenderScalePoint();
s32 PS4_SYSV_ABI sceFontStringGetTerminateCode();
s32 PS4_SYSV_ABI sceFontStringGetTerminateOrder();
s32 PS4_SYSV_ABI sceFontStringGetWritingForm();
s32 PS4_SYSV_ABI sceFontStringRefersRenderCharacters();
s32 PS4_SYSV_ABI sceFontStringRefersTextCharacters();
s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectSlant(OrbisFontStyleFrame* styleFrame,
float* slantRatio);
s32 PS4_SYSV_ABI sceFontStyleFrameGetEffectWeight(OrbisFontStyleFrame* fontStyleFrame,
float* weightXScale, float* weightYScale,
uint32_t* mode);
s32 PS4_SYSV_ABI sceFontStyleFrameGetResolutionDpi();
s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePixel(OrbisFontStyleFrame* styleFrame, float* w,
float* h);
s32 PS4_SYSV_ABI sceFontStyleFrameGetScalePoint();
s32 PS4_SYSV_ABI sceFontStyleFrameInit();
s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectSlant();
s32 PS4_SYSV_ABI sceFontStyleFrameSetEffectWeight();
s32 PS4_SYSV_ABI sceFontStyleFrameSetResolutionDpi();
s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePixel();
s32 PS4_SYSV_ABI sceFontStyleFrameSetScalePoint();
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectSlant();
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetEffectWeight();
s32 PS4_SYSV_ABI sceFontStyleFrameUnsetScale();
s32 PS4_SYSV_ABI sceFontSupportExternalFonts();
s32 PS4_SYSV_ABI sceFontSupportGlyphs();
s32 PS4_SYSV_ABI sceFontSupportSystemFonts();
s32 PS4_SYSV_ABI sceFontTextCodesStepBack();
s32 PS4_SYSV_ABI sceFontTextCodesStepNext();
s32 PS4_SYSV_ABI sceFontTextSourceInit();
s32 PS4_SYSV_ABI sceFontTextSourceRewind();
s32 PS4_SYSV_ABI sceFontTextSourceSetDefaultFont();
s32 PS4_SYSV_ABI sceFontTextSourceSetWritingForm();
s32 PS4_SYSV_ABI sceFontUnbindRenderer();
s32 PS4_SYSV_ABI sceFontWordsFindWordCharacters();
s32 PS4_SYSV_ABI sceFontWritingGetRenderMetrics();
s32 PS4_SYSV_ABI sceFontWritingInit();
s32 PS4_SYSV_ABI sceFontWritingLineClear();
s32 PS4_SYSV_ABI sceFontWritingLineGetOrderingSpace();
s32 PS4_SYSV_ABI sceFontWritingLineGetRenderMetrics();
s32 PS4_SYSV_ABI sceFontWritingLineRefersRenderStep();
s32 PS4_SYSV_ABI sceFontWritingLineWritesOrder();
s32 PS4_SYSV_ABI sceFontWritingRefersRenderStep();
s32 PS4_SYSV_ABI sceFontWritingRefersRenderStepCharacter();
s32 PS4_SYSV_ABI sceFontWritingSetMaskInvisible();
s32 PS4_SYSV_ABI Func_00F4D778F1C88CB3();
s32 PS4_SYSV_ABI Func_03C650025FBB0DE7();
s32 PS4_SYSV_ABI Func_07EAB8A163B27E1A();
s32 PS4_SYSV_ABI Func_09408E88E4F97CE3();
s32 PS4_SYSV_ABI Func_09F92905ED82A814();
s32 PS4_SYSV_ABI Func_0D142CEE1AB21ABE();
s32 PS4_SYSV_ABI Func_14BD2E9E119C16F2();
s32 PS4_SYSV_ABI Func_1AC53C9EDEAE8D75();
s32 PS4_SYSV_ABI Func_1D401185D5E24C3D();
s32 PS4_SYSV_ABI Func_1E83CD20C2CC996F();
s32 PS4_SYSV_ABI Func_314B1F765B9FE78A();
s32 PS4_SYSV_ABI Func_350E6725FEDE29E1();
s32 PS4_SYSV_ABI Func_3DB773F0A604BF39();
s32 PS4_SYSV_ABI Func_4FF49DD21E311B1C();
s32 PS4_SYSV_ABI Func_526287664A493981();
s32 PS4_SYSV_ABI Func_55CA718DBC84A6E9();
s32 PS4_SYSV_ABI Func_563FC5F0706A8B4D();
s32 PS4_SYSV_ABI Func_569E2ECD34290F45();
s32 PS4_SYSV_ABI Func_5A04775B6BE47685();
s32 PS4_SYSV_ABI Func_5FD93BCAB6F79750();
s32 PS4_SYSV_ABI Func_62B5398F864BD3B4();
s32 PS4_SYSV_ABI Func_6F9010294D822367();
s32 PS4_SYSV_ABI Func_7757E947423A7A67();
s32 PS4_SYSV_ABI Func_7E06BA52077F54FA();
s32 PS4_SYSV_ABI Func_93B36DEA021311D6();
s32 PS4_SYSV_ABI Func_94B0891E7111598A();
s32 PS4_SYSV_ABI Func_9785C9128C2FE7CD();
s32 PS4_SYSV_ABI Func_97DFBC9B65FBC0E1();
s32 PS4_SYSV_ABI Func_ACD9717405D7D3CA();
s32 PS4_SYSV_ABI Func_B19A8AEC3FD4F16F();
s32 PS4_SYSV_ABI Func_C10F488AD7CF103D();
s32 PS4_SYSV_ABI Func_D0C8B5FF4A6826C7();
s32 PS4_SYSV_ABI Func_E48D3CD01C342A33();
s32 PS4_SYSV_ABI Func_EAC96B2186B71E14();
s32 PS4_SYSV_ABI Func_FE4788A96EF46256();
s32 PS4_SYSV_ABI Func_FE7E5AE95D3058F5();
void RegisterlibSceFont(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Font

View File

@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/libraries/error_codes.h"
constexpr int ORBIS_FONT_ERROR_FATAL = 0x80460001;
constexpr int ORBIS_FONT_ERROR_INVALID_PARAMETER = 0x80460002;
constexpr int ORBIS_FONT_ERROR_INVALID_MEMORY = 0x80460003;
constexpr int ORBIS_FONT_ERROR_INVALID_LIBRARY = 0x80460004;
constexpr int ORBIS_FONT_ERROR_INVALID_FONT_HANDLE = 0x80460005;
constexpr int ORBIS_FONT_ERROR_INVALID_GLYPH = 0x80460006;
constexpr int ORBIS_FONT_ERROR_INVALID_RENDERER = 0x80460007;
constexpr int ORBIS_FONT_ERROR_INVALID_TEXT_SOURCE = 0x80460008;
constexpr int ORBIS_FONT_ERROR_INVALID_STRING = 0x80460009;
constexpr int ORBIS_FONT_ERROR_INVALID_WRITING = 0x8046000A;
constexpr int ORBIS_FONT_ERROR_INVALID_WORDS = 0x8046000B;
constexpr int ORBIS_FONT_ERROR_ALLOCATION_FAILED = 0x80460010;
constexpr int ORBIS_FONT_ERROR_FS_OPEN_FAILED = 0x80460011;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_LIBRARY = 0x80460018;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FORMAT = 0x80460019;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FUNCTION = 0x80460020;
constexpr int ORBIS_FONT_ERROR_ALREADY_SPECIFIED = 0x80460021;
constexpr int ORBIS_FONT_ERROR_ALREADY_ATTACHED = 0x80460022;
constexpr int ORBIS_FONT_ERROR_ALREADY_OPENED = 0x80460023;
constexpr int ORBIS_FONT_ERROR_NOT_ATTACHED_CACHE_BUFFER = 0x80460025;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_FONTSET = 0x80460031;
constexpr int ORBIS_FONT_ERROR_FONT_OPEN_MAX = 0x80460033;
constexpr int ORBIS_FONT_ERROR_FONT_OPEN_FAILED = 0x80460036;
constexpr int ORBIS_FONT_ERROR_FONT_CLOSE_FAILED = 0x80460037;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_TYPOGRAPHY = 0x80460040;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_CODE = 0x80460041;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_GLYPH = 0x80460042;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_SCRIPT = 0x80460043;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_LANGUAGE = 0x80460044;
constexpr int ORBIS_FONT_ERROR_NO_SUPPORT_SURFACE = 0x80460050;
constexpr int ORBIS_FONT_ERROR_UNSET_PARAMETER = 0x80460058;
constexpr int ORBIS_FONT_ERROR_FUNCTIONAL_LIMIT = 0x8046005C;
constexpr int ORBIS_FONT_ERROR_ALREADY_BOUND_RENDERER = 0x80460060;
constexpr int ORBIS_FONT_ERROR_NOT_BOUND_RENDERER = 0x80460061;
constexpr int ORBIS_FONT_ERROR_RENDERER_ALLOCATION_FAILED = 0x80460063;
constexpr int ORBIS_FONT_ERROR_RENDERER_ALLOCATION_LIMITED = 0x80460064;
constexpr int ORBIS_FONT_ERROR_RENDERER_RENDER_FAILED = 0x80460065;

View File

@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/font/fontft.h"
#include "core/libraries/libs.h"
namespace Libraries::FontFt {
s32 PS4_SYSV_ABI sceFontFtInitAliases() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSetAliasFont() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSetAliasPath() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportBdf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportCid() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportFontFormats() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportOpenType() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeOtf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeTtf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportPcf() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportPfr() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportSystemFonts() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportTrueType() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportTrueTypeGx() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportType1() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportType42() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtSupportWinFonts() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontFtTermAliases() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontSelectGlyphsFt() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontSelectLibraryFt() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFontSelectRendererFt() {
LOG_ERROR(Lib_FontFt, "(STUBBED) called");
return ORBIS_OK;
}
void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("e60aorDdpB8", "libSceFontFt", 1, "libSceFontFt", sceFontFtInitAliases);
LIB_FUNCTION("BxcmiMc3UaA", "libSceFontFt", 1, "libSceFontFt", sceFontFtSetAliasFont);
LIB_FUNCTION("MEWjebIzDEI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSetAliasPath);
LIB_FUNCTION("ZcQL0iSjvFw", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportBdf);
LIB_FUNCTION("LADHEyFTxRQ", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportCid);
LIB_FUNCTION("+jqQjsancTs", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportFontFormats);
LIB_FUNCTION("oakL15-mBtc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenType);
LIB_FUNCTION("dcQeaDr8UJc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenTypeOtf);
LIB_FUNCTION("2KXS-HkZT3c", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportOpenTypeTtf);
LIB_FUNCTION("H0mJnhKwV-s", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportPcf);
LIB_FUNCTION("S2mw3sYplAI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportPfr);
LIB_FUNCTION("+ehNXJPUyhk", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportSystemFonts);
LIB_FUNCTION("4BAhDLdrzUI", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportTrueType);
LIB_FUNCTION("Utlzbdf+g9o", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportTrueTypeGx);
LIB_FUNCTION("nAfQ6qaL1fU", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportType1);
LIB_FUNCTION("X9+pzrGtBus", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportType42);
LIB_FUNCTION("w0hI3xsK-hc", "libSceFontFt", 1, "libSceFontFt", sceFontFtSupportWinFonts);
LIB_FUNCTION("w5sfH9r8ZJ4", "libSceFontFt", 1, "libSceFontFt", sceFontFtTermAliases);
LIB_FUNCTION("ojW+VKl4Ehs", "libSceFontFt", 1, "libSceFontFt", sceFontSelectGlyphsFt);
LIB_FUNCTION("oM+XCzVG3oM", "libSceFontFt", 1, "libSceFontFt", sceFontSelectLibraryFt);
LIB_FUNCTION("Xx974EW-QFY", "libSceFontFt", 1, "libSceFontFt", sceFontSelectRendererFt);
};
} // namespace Libraries::FontFt

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::FontFt {
s32 PS4_SYSV_ABI sceFontFtInitAliases();
s32 PS4_SYSV_ABI sceFontFtSetAliasFont();
s32 PS4_SYSV_ABI sceFontFtSetAliasPath();
s32 PS4_SYSV_ABI sceFontFtSupportBdf();
s32 PS4_SYSV_ABI sceFontFtSupportCid();
s32 PS4_SYSV_ABI sceFontFtSupportFontFormats();
s32 PS4_SYSV_ABI sceFontFtSupportOpenType();
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeOtf();
s32 PS4_SYSV_ABI sceFontFtSupportOpenTypeTtf();
s32 PS4_SYSV_ABI sceFontFtSupportPcf();
s32 PS4_SYSV_ABI sceFontFtSupportPfr();
s32 PS4_SYSV_ABI sceFontFtSupportSystemFonts();
s32 PS4_SYSV_ABI sceFontFtSupportTrueType();
s32 PS4_SYSV_ABI sceFontFtSupportTrueTypeGx();
s32 PS4_SYSV_ABI sceFontFtSupportType1();
s32 PS4_SYSV_ABI sceFontFtSupportType42();
s32 PS4_SYSV_ABI sceFontFtSupportWinFonts();
s32 PS4_SYSV_ABI sceFontFtTermAliases();
s32 PS4_SYSV_ABI sceFontSelectGlyphsFt();
s32 PS4_SYSV_ABI sceFontSelectLibraryFt();
s32 PS4_SYSV_ABI sceFontSelectRendererFt();
void RegisterlibSceFontFt(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::FontFt

View File

@ -140,7 +140,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
// Create a file if it doesn't exist
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Write);
Common::FS::IOFile out(file->m_host_name, Common::FS::FileAccessMode::Create);
}
} else if (!exists) {
// If we're not creating a file, and it doesn't exist, return ENOENT
@ -205,22 +205,30 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
}
if (read) {
// Read only
// Open exclusively for reading
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Read);
} else if (read_only) {
// Can't open files with write/read-write access in a read only directory
h->DeleteHandle(handle);
*__Error() = POSIX_EROFS;
return -1;
} else if (append) {
// Append can be specified with rdwr or write, but we treat it as a separate mode.
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
} else if (write) {
// Write only
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
if (append) {
// Open exclusively for appending
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Append);
} else {
// Open exclusively for writing
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::Write);
}
} else if (rdwr) {
// Read and write
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
if (append) {
// Open for reading and appending
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadAppend);
} else {
// Open for reading and writing
e = file->f.Open(file->m_host_name, Common::FS::FileAccessMode::ReadWrite);
}
}
}
@ -354,6 +362,12 @@ s64 PS4_SYSV_ABI readv(s32 fd, const OrbisKernelIovec* iov, s32 iovcnt) {
}
return result;
}
if (file->f.IsWriteOnly()) {
*__Error() = POSIX_EBADF;
return -1;
}
s64 total_read = 0;
for (s32 i = 0; i < iovcnt; i++) {
total_read += ReadFile(file->f, iov[i].iov_base, iov[i].iov_len);
@ -509,6 +523,12 @@ s64 PS4_SYSV_ABI read(s32 fd, void* buf, u64 nbytes) {
// Socket functions handle errnos internally.
return file->socket->ReceivePacket(buf, nbytes, 0, nullptr, 0);
}
if (file->f.IsWriteOnly()) {
*__Error() = POSIX_EBADF;
return -1;
}
return ReadFile(file->f, buf, nbytes);
}
@ -620,17 +640,29 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
*__Error() = POSIX_ENOENT;
return -1;
}
// get the difference between file clock and system clock
const auto now_sys = std::chrono::system_clock::now();
const auto now_file = std::filesystem::file_time_type::clock::now();
// calculate the file modified time
const auto mtime = std::filesystem::last_write_time(path_name);
const auto mtimestamp = now_sys + (mtime - now_file);
if (std::filesystem::is_directory(path_name)) {
sb->st_mode = 0000777u | 0040000u;
sb->st_size = 65536;
sb->st_blksize = 65536;
sb->st_blocks = 128;
sb->st_mtim.tv_sec =
std::chrono::duration_cast<std::chrono::seconds>(mtimestamp.time_since_epoch()).count();
// TODO incomplete
} else {
sb->st_mode = 0000777u | 0100000u;
sb->st_size = static_cast<s64>(std::filesystem::file_size(path_name));
sb->st_blksize = 512;
sb->st_blocks = (sb->st_size + 511) / 512;
sb->st_mtim.tv_sec =
std::chrono::duration_cast<std::chrono::seconds>(mtimestamp.time_since_epoch()).count();
// TODO incomplete
}
@ -801,11 +833,7 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(src_path);
if (file) {
// We need to force ReadWrite if the file had Write access before
// Otherwise f.Open will clear the file contents.
auto access_mode = file->f.GetAccessMode() == Common::FS::FileAccessMode::Write
? Common::FS::FileAccessMode::ReadWrite
: file->f.GetAccessMode();
auto access_mode = file->f.GetAccessMode();
file->f.Close();
std::filesystem::remove(src_path);
file->f.Open(dst_path, access_mode);
@ -855,6 +883,11 @@ s64 PS4_SYSV_ABI posix_preadv(s32 fd, OrbisKernelIovec* iov, s32 iovcnt, s64 off
return result;
}
if (file->f.IsWriteOnly()) {
*__Error() = POSIX_EBADF;
return -1;
}
const s64 pos = file->f.Tell();
SCOPE_EXIT {
file->f.Seek(pos);

View File

@ -236,15 +236,24 @@ s32 PS4_SYSV_ABI sceKernelSetGPO() {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelGetAllowedSdkVersionOnSystem(s32* ver) {
if (ver == nullptr) {
return ORBIS_KERNEL_ERROR_EINVAL;
}
// Returns the highest game SDK version this PS4 allows.
*ver = CURRENT_FIRMWARE_VERSION | 0xfff;
LOG_INFO(Lib_Kernel, "called, returned sw version: {}", *ver);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelGetSystemSwVersion(SwVersionStruct* ret) {
if (ret == nullptr) {
return ORBIS_OK; // but why?
return ORBIS_OK;
}
ASSERT(ret->struct_size == 40);
u32 fake_fw = Common::ElfInfo::Instance().RawFirmwareVer();
u32 fake_fw = CURRENT_FIRMWARE_VERSION;
ret->hex_representation = fake_fw;
std::snprintf(ret->text_representation, 28, "%2x.%03x.%03x", fake_fw >> 0x18,
fake_fw >> 0xc & 0xfff, fake_fw & 0xfff); // why %2x?
fake_fw >> 0xc & 0xfff, fake_fw & 0xfff);
LOG_INFO(Lib_Kernel, "called, returned sw version: {}", ret->text_representation);
return ORBIS_OK;
}
@ -257,9 +266,13 @@ const char** PS4_SYSV_ABI getargv() {
return entry_params.argv;
}
s32 PS4_SYSV_ABI get_authinfo(int pid, AuthInfoData* p2) {
s32 PS4_SYSV_ABI get_authinfo(s32 pid, AuthInfoData* p2) {
LOG_WARNING(Lib_Kernel, "(STUBBED) called, pid: {}", pid);
if ((pid != 0) && (pid != GLOBAL_PID)) {
if (p2 == nullptr) {
*Kernel::__Error() = POSIX_EPERM;
return -1;
}
if (pid != 0 && pid != GLOBAL_PID) {
*Kernel::__Error() = POSIX_ESRCH;
return -1;
}
@ -269,6 +282,22 @@ s32 PS4_SYSV_ABI get_authinfo(int pid, AuthInfoData* p2) {
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceKernelGetAppInfo(s32 pid, OrbisKernelAppInfo* app_info) {
LOG_WARNING(Lib_Kernel, "(STUBBED) called, pid: {}", pid);
if (pid != GLOBAL_PID) {
return ORBIS_KERNEL_ERROR_EPERM;
}
if (app_info == nullptr) {
return ORBIS_OK;
}
auto& game_info = Common::ElfInfo::Instance();
*app_info = {};
app_info->has_param_sfo = 1;
strncpy(app_info->cusa_name, game_info.GameSerial().data(), 10);
return ORBIS_OK;
}
void RegisterLib(Core::Loader::SymbolsResolver* sym) {
service_thread = std::jthread{KernelServiceThread};
@ -285,8 +314,10 @@ void RegisterLib(Core::Loader::SymbolsResolver* sym) {
LIB_OBJ("f7uOxY9mM1U", "libkernel", 1, "libkernel", &g_stack_chk_guard);
LIB_FUNCTION("D4yla3vx4tY", "libkernel", 1, "libkernel", sceKernelError);
LIB_FUNCTION("YeU23Szo3BM", "libkernel", 1, "libkernel", sceKernelGetAllowedSdkVersionOnSystem);
LIB_FUNCTION("Mv1zUObHvXI", "libkernel", 1, "libkernel", sceKernelGetSystemSwVersion);
LIB_FUNCTION("igMefp4SAv0", "libkernel", 1, "libkernel", get_authinfo);
LIB_FUNCTION("G-MYv5erXaU", "libkernel", 1, "libkernel", sceKernelGetAppInfo);
LIB_FUNCTION("PfccT7qURYE", "libkernel", 1, "libkernel", kernel_ioctl);
LIB_FUNCTION("wW+k21cmbwQ", "libkernel", 1, "libkernel", kernel_ioctl);
LIB_FUNCTION("JGfTMBOdUJo", "libkernel", 1, "libkernel", sceKernelGetFsSandboxRandomWord);

View File

@ -36,6 +36,8 @@ struct OrbisWrapperImpl<PS4_SYSV_ABI R (*)(Args...), f> {
#define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl<decltype(&(func)), func>::wrap)
#define CURRENT_FIRMWARE_VERSION 0x13020011
s32* PS4_SYSV_ABI __Error();
struct SwVersionStruct {
@ -51,6 +53,30 @@ struct AuthInfoData {
u64 ucred[8];
};
struct OrbisKernelTitleWorkaround {
s32 version;
s32 align;
u64 ids[2];
};
struct OrbisKernelAppInfo {
s32 app_id;
s32 mmap_flags;
s32 attribute_exe;
s32 attribute2;
char cusa_name[10];
u8 debug_level;
u8 slv_flags;
u8 mini_app_dmem_flags;
u8 render_mode;
u8 mdbg_out;
u8 required_hdcp_type;
u64 preload_prx_flags;
s32 attribute1;
s32 has_param_sfo;
OrbisKernelTitleWorkaround title_workaround;
};
void RegisterLib(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Kernel

View File

@ -21,8 +21,22 @@ s32 PS4_SYSV_ABI sceKernelIsNeoMode() {
Common::ElfInfo::Instance().GetPSFAttributes().support_neo_mode;
}
s32 PS4_SYSV_ABI sceKernelHasNeoMode() {
return Config::isNeoModeConsole();
}
s32 PS4_SYSV_ABI sceKernelGetMainSocId() {
// These hardcoded values are based on hardware observations.
// Different models of PS4/PS4 Pro likely return slightly different values.
LOG_DEBUG(Lib_Kernel, "called");
if (Config::isNeoModeConsole()) {
return 0x740f30;
}
return 0x710f10;
}
s32 PS4_SYSV_ABI sceKernelGetCompiledSdkVersion(s32* ver) {
s32 version = Common::ElfInfo::Instance().RawFirmwareVer();
s32 version = Common::ElfInfo::Instance().CompiledSdkVer();
*ver = version;
return (version >= 0) ? ORBIS_OK : ORBIS_KERNEL_ERROR_EINVAL;
}
@ -31,6 +45,11 @@ s32 PS4_SYSV_ABI sceKernelGetCpumode() {
return 0;
}
s32 PS4_SYSV_ABI sceKernelGetCurrentCpu() {
LOG_DEBUG(Lib_Kernel, "called");
return 0;
}
void* PS4_SYSV_ABI sceKernelGetProcParam() {
auto* linker = Common::Singleton<Core::Linker>::Instance();
return linker->GetProcParam();
@ -208,7 +227,10 @@ void RegisterProcess(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("xeu-pV8wkKs", "libkernel", 1, "libkernel", sceKernelIsInSandbox);
LIB_FUNCTION("WB66evu8bsU", "libkernel", 1, "libkernel", sceKernelGetCompiledSdkVersion);
LIB_FUNCTION("WslcK1FQcGI", "libkernel", 1, "libkernel", sceKernelIsNeoMode);
LIB_FUNCTION("rNRtm1uioyY", "libkernel", 1, "libkernel", sceKernelHasNeoMode);
LIB_FUNCTION("0vTn5IDMU9A", "libkernel", 1, "libkernel", sceKernelGetMainSocId);
LIB_FUNCTION("VOx8NGmHXTs", "libkernel", 1, "libkernel", sceKernelGetCpumode);
LIB_FUNCTION("g0VTBxfJyu0", "libkernel", 1, "libkernel", sceKernelGetCurrentCpu);
LIB_FUNCTION("959qrazPIrg", "libkernel", 1, "libkernel", sceKernelGetProcParam);
LIB_FUNCTION("wzvqT4UqKX8", "libkernel", 1, "libkernel", sceKernelLoadStartModule);
LIB_FUNCTION("LwG8g3niqwA", "libkernel", 1, "libkernel", sceKernelDlsym);

View File

@ -354,6 +354,8 @@ void RegisterCond(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("Op8TBGY5KHg", "libkernel", 1, "libkernel", posix_pthread_cond_wait);
LIB_FUNCTION("mkx2fVhNMsg", "libkernel", 1, "libkernel", posix_pthread_cond_broadcast);
LIB_FUNCTION("2MOy+rUfuhQ", "libkernel", 1, "libkernel", posix_pthread_cond_signal);
LIB_FUNCTION("RXXqi4CtF8w", "libkernel", 1, "libkernel", posix_pthread_cond_destroy);
LIB_FUNCTION("27bAgiJmOh0", "libkernel", 1, "libkernel", posix_pthread_cond_timedwait);
LIB_FUNCTION("mKoTx03HRWA", "libkernel", 1, "libkernel", posix_pthread_condattr_init);
LIB_FUNCTION("dJcuQVn6-Iw", "libkernel", 1, "libkernel", posix_pthread_condattr_destroy);

View File

@ -442,6 +442,8 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("ltCfaGr2JGE", "libkernel", 1, "libkernel", posix_pthread_mutex_destroy);
LIB_FUNCTION("dQHWEsJtoE4", "libkernel", 1, "libkernel", posix_pthread_mutexattr_init);
LIB_FUNCTION("mDmgMOGVUqg", "libkernel", 1, "libkernel", posix_pthread_mutexattr_settype);
LIB_FUNCTION("HF7lK46xzjY", "libkernel", 1, "libkernel", posix_pthread_mutexattr_destroy);
LIB_FUNCTION("K-jXhbt2gn4", "libkernel", 1, "libkernel", posix_pthread_mutex_trylock);
// Orbis
LIB_FUNCTION("cmo1RIYva9o", "libkernel", 1, "libkernel", ORBIS(scePthreadMutexInit));

View File

@ -663,6 +663,9 @@ void RegisterThread(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("Z4QosVuAsA0", "libkernel", 1, "libkernel", posix_pthread_once);
LIB_FUNCTION("EotR8a3ASf4", "libkernel", 1, "libkernel", posix_pthread_self);
LIB_FUNCTION("OxhIB8LB-PQ", "libkernel", 1, "libkernel", posix_pthread_create);
LIB_FUNCTION("+U1R4WtXvoc", "libkernel", 1, "libkernel", posix_pthread_detach);
LIB_FUNCTION("7Xl257M4VNI", "libkernel", 1, "libkernel", posix_pthread_equal);
LIB_FUNCTION("h9CcP3J0oVM", "libkernel", 1, "libkernel", posix_pthread_join);
LIB_FUNCTION("Jb2uGFMr688", "libkernel", 1, "libkernel", posix_pthread_getaffinity_np);
LIB_FUNCTION("5KWrg7-ZqvE", "libkernel", 1, "libkernel", posix_pthread_setaffinity_np);
LIB_FUNCTION("3eqs37G74-s", "libkernel", 1, "libkernel", posix_pthread_getthreadid_np);

View File

@ -9,6 +9,35 @@
namespace Libraries::Http {
static bool g_isHttpInitialized = true; // TODO temp always inited
void NormalizeAndAppendPath(char* dest, char* src) {
char* lastSlash;
u64 length;
lastSlash = strrchr(dest, '/');
if (lastSlash == NULL) {
length = strlen(dest);
dest[length] = '/';
dest[length + 1] = '\0';
} else {
lastSlash[1] = '\0';
}
if (*src == '/') {
dest[0] = '\0';
}
length = strnlen(dest, 0x3fff);
strncat(dest, src, 0x3fff - length);
return;
}
int HttpRequestInternal_Acquire(HttpRequestInternal** outRequest, u32 requestId) {
return 0; // TODO dummy
}
int HttpRequestInternal_Release(HttpRequestInternal* request) {
return 0; // TODO dummy
}
int PS4_SYSV_ABI sceHttpAbortRequest() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
@ -34,8 +63,9 @@ int PS4_SYSV_ABI sceHttpAddQuery() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpAddRequestHeader() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode) {
LOG_ERROR(Lib_Http, "(STUBBED) called id= {} name = {} value = {} mode = {}", id,
std::string(name), std::string(value), mode);
return ORBIS_OK;
}
@ -84,8 +114,9 @@ int PS4_SYSV_ABI sceHttpCreateConnection() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive) {
LOG_ERROR(Lib_Http, "(STUBBED) called tmpid = {} url = {} enableKeepalive = {}", tmplId,
std::string(url), enableKeepalive ? 1 : 0);
return ORBIS_OK;
}
@ -104,8 +135,10 @@ int PS4_SYSV_ABI sceHttpCreateRequest2() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpCreateRequestWithURL() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
u64 contentLength) {
LOG_ERROR(Lib_Http, "(STUBBED) called connId = {} method = {} url={} contentLength={}", connId,
method, url, contentLength);
return ORBIS_OK;
}
@ -184,7 +217,7 @@ int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders() {
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_FAIL;
}
@ -254,12 +287,42 @@ int PS4_SYSV_ABI sceHttpGetResponseContentLength() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpGetStatusCode() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {}", reqId);
#if 0
if (!g_isHttpInitialized)
return ORBIS_HTTP_ERROR_BEFORE_INIT;
if (statusCode == nullptr)
return ORBIS_HTTP_ERROR_INVALID_VALUE;
int ret = 0;
// Lookup HttpRequestInternal by reqId
HttpRequestInternal* request = nullptr;
ret = HttpRequestInternal_Acquire(&request, reqId);
if (ret < 0)
return ret;
request->m_mutex.lock();
if (request->state > 0x11) {
if (request->state == 0x16) {
ret = request->errorCode;
} else {
*statusCode = request->httpStatusCode;
ret = 0;
}
} else {
ret = ORBIS_HTTP_ERROR_BEFORE_SEND;
}
request->m_mutex.unlock();
HttpRequestInternal_Release(request);
return ret;
#else
return ORBIS_OK;
#endif
}
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolSize) {
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize) {
LOG_ERROR(Lib_Http, "(DUMMY) called libnetMemId = {} libsslCtxId = {} poolSize = {}",
libnetMemId, libsslCtxId, poolSize);
// return a value >1
@ -267,14 +330,104 @@ int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolS
return ++id;
}
int PS4_SYSV_ABI sceHttpParseResponseHeader() {
int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
const char** fieldValue, u64* valueLen) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpParseStatusLine() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
int32_t* httpMinorVer, int32_t* responseCode,
const char** reasonPhrase, u64* phraseLen) {
if (!statusLine) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
if (!httpMajorVer || !httpMinorVer || !responseCode || !reasonPhrase || !phraseLen) {
LOG_ERROR(Lib_Http, "Invalid value");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_VALUE;
}
*httpMajorVer = 0;
*httpMinorVer = 0;
if (lineLen < 8) {
LOG_ERROR(Lib_Http, "Linelen is smaller than 8");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
if (strncmp(statusLine, "HTTP/", 5) != 0) {
LOG_ERROR(Lib_Http, "statusLine doesn't start with HTTP/");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
u64 index = 5;
if (!isdigit(statusLine[index])) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
while (isdigit(statusLine[index])) {
*httpMajorVer = *httpMajorVer * 10 + (statusLine[index] - '0');
index++;
}
if (statusLine[index] != '.') {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
index++;
if (!isdigit(statusLine[index])) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
while (isdigit(statusLine[index])) {
*httpMinorVer = *httpMinorVer * 10 + (statusLine[index] - '0');
index++;
}
if (statusLine[index] != ' ') {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
index++;
// Validate and parse the 3-digit HTTP response code
if (lineLen - index < 3 || !isdigit(statusLine[index]) || !isdigit(statusLine[index + 1]) ||
!isdigit(statusLine[index + 2])) {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
*responseCode = (statusLine[index] - '0') * 100 + (statusLine[index + 1] - '0') * 10 +
(statusLine[index + 2] - '0');
index += 3;
if (statusLine[index] != ' ') {
LOG_ERROR(Lib_Http, "Invalid response");
return ORBIS_HTTP_ERROR_PARSE_HTTP_INVALID_RESPONSE;
}
index++;
// Set the reason phrase start position
*reasonPhrase = &statusLine[index];
u64 phraseStart = index;
while (index < lineLen && statusLine[index] != '\n') {
index++;
}
// Determine the length of the reason phrase, excluding trailing \r if present
if (index == phraseStart) {
*phraseLen = 0;
} else {
*phraseLen =
(statusLine[index - 1] == '\r') ? (index - phraseStart - 1) : (index - phraseStart);
}
// Return the number of bytes processed
return index + 1;
}
int PS4_SYSV_ABI sceHttpReadData() {
@ -317,8 +470,8 @@ int PS4_SYSV_ABI sceHttpsEnableOptionPrivate() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpSendRequest() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size) {
LOG_ERROR(Lib_Http, "(STUBBED) called reqId = {} size = {}", reqId, size);
return ORBIS_OK;
}
@ -548,7 +701,8 @@ int PS4_SYSV_ABI sceHttpUnsetEpoll() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriBuild() {
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
const OrbisHttpUriElement* srcElement, u32 option) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
}
@ -563,13 +717,97 @@ int PS4_SYSV_ABI sceHttpUriEscape() {
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriMerge() {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
u64 prepare, u32 option) {
u64 requiredLength;
int returnValue;
u64 baseUrlLength;
u64 relativeUriLength;
u64 totalLength;
u64 combinedLength;
int parseResult;
u64 localSizeRelativeUri;
u64 localSizeBaseUrl;
OrbisHttpUriElement parsedUriElement;
if (option != 0 || url == NULL || relativeUri == NULL) {
LOG_ERROR(Lib_Http, "Invalid value");
return ORBIS_HTTP_ERROR_INVALID_VALUE;
}
returnValue = sceHttpUriParse(NULL, url, NULL, &localSizeBaseUrl, 0);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
returnValue = sceHttpUriParse(NULL, relativeUri, NULL, &localSizeRelativeUri, 0);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
baseUrlLength = strnlen(url, 0x3fff);
relativeUriLength = strnlen(relativeUri, 0x3fff);
requiredLength = localSizeBaseUrl + 2 + (relativeUriLength + baseUrlLength) * 2;
if (require) {
*require = requiredLength;
}
if (mergedUrl == NULL) {
return ORBIS_OK;
}
if (prepare < requiredLength) {
LOG_ERROR(Lib_Http, "Error Out of memory");
return ORBIS_HTTP_ERROR_OUT_OF_MEMORY;
}
totalLength = strnlen(url, 0x3fff);
baseUrlLength = strnlen(relativeUri, 0x3fff);
combinedLength = totalLength + 1 + baseUrlLength;
relativeUriLength = prepare - combinedLength;
returnValue =
sceHttpUriParse(&parsedUriElement, relativeUri, mergedUrl + totalLength + baseUrlLength + 1,
&localSizeRelativeUri, relativeUriLength);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
if (parsedUriElement.scheme == NULL) {
strncpy(mergedUrl, relativeUri, requiredLength);
if (require) {
*require = strnlen(relativeUri, 0x3fff) + 1;
}
return ORBIS_OK;
}
returnValue =
sceHttpUriParse(&parsedUriElement, url, mergedUrl + totalLength + baseUrlLength + 1,
&localSizeBaseUrl, relativeUriLength);
if (returnValue < 0) {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
combinedLength += localSizeBaseUrl;
strncpy(mergedUrl + combinedLength, parsedUriElement.path, prepare - combinedLength);
NormalizeAndAppendPath(mergedUrl + combinedLength, relativeUri);
returnValue = sceHttpUriBuild(mergedUrl, 0, ~(baseUrlLength + totalLength) + prepare,
&parsedUriElement, 0x3f);
if (returnValue >= 0) {
return ORBIS_OK;
} else {
LOG_ERROR(Lib_Http, "returning {:#x}", returnValue);
return returnValue;
}
}
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
size_t* require, size_t prepare) {
u64* require, u64 prepare) {
LOG_INFO(Lib_Http, "srcUri = {}", std::string(srcUri));
if (!srcUri) {
LOG_ERROR(Lib_Http, "invalid url");
@ -586,10 +824,10 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
}
// Track the total required buffer size
size_t requiredSize = 0;
u64 requiredSize = 0;
// Parse the scheme (e.g., "http:", "https:", "file:")
size_t schemeLength = 0;
u64 schemeLength = 0;
while (srcUri[schemeLength] && srcUri[schemeLength] != ':') {
if (!isalnum(srcUri[schemeLength])) {
LOG_ERROR(Lib_Http, "invalid url");
@ -611,7 +849,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
requiredSize += schemeLength + 1;
// Move past the scheme and ':' character
size_t offset = schemeLength + 1;
u64 offset = schemeLength + 1;
// Check if "//" appears after the scheme
if (strncmp(srcUri + offset, "//", 2) == 0) {
@ -638,7 +876,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the path (everything after the slashes)
char* pathStart = (char*)srcUri + offset;
size_t pathLength = 0;
u64 pathLength = 0;
while (pathStart[pathLength] && pathStart[pathLength] != '?' &&
pathStart[pathLength] != '#') {
pathLength++;
@ -689,7 +927,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
hostStart++;
}
size_t hostLength = 0;
u64 hostLength = 0;
while (hostStart[hostLength] && hostStart[hostLength] != '/' &&
hostStart[hostLength] != '?' && hostStart[hostLength] != ':') {
hostLength++;
@ -714,7 +952,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the port (if present)
if (hostStart[hostLength] == ':') {
char* portStart = hostStart + hostLength + 1;
size_t portLength = 0;
u64 portLength = 0;
while (portStart[portLength] && isdigit(portStart[portLength])) {
portLength++;
}
@ -754,7 +992,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the path (if present)
if (srcUri[offset] == '/') {
char* pathStart = (char*)srcUri + offset;
size_t pathLength = 0;
u64 pathLength = 0;
while (pathStart[pathLength] && pathStart[pathLength] != '?' &&
pathStart[pathLength] != '#') {
pathLength++;
@ -780,7 +1018,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the query (if present)
if (srcUri[offset] == '?') {
char* queryStart = (char*)srcUri + offset + 1;
size_t queryLength = 0;
u64 queryLength = 0;
while (queryStart[queryLength] && queryStart[queryLength] != '#') {
queryLength++;
}
@ -805,7 +1043,7 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
// Parse the fragment (if present)
if (srcUri[offset] == '#') {
char* fragmentStart = (char*)srcUri + offset + 1;
size_t fragmentLength = 0;
u64 fragmentLength = 0;
while (fragmentStart[fragmentLength]) {
fragmentLength++;
}
@ -833,12 +1071,12 @@ int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, v
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize) {
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
}
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in) {
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in) {
LOG_ERROR(Lib_Http, "(STUBBED) called");
return ORBIS_OK;
}

View File

@ -3,6 +3,7 @@
#pragma once
#include <mutex>
#include "common/types.h"
#include "core/libraries/network/ssl.h"
@ -25,6 +26,12 @@ struct OrbisHttpUriElement {
u8 reserved[10];
};
struct HttpRequestInternal {
int state; // +0x20
int errorCode; // +0x28
int httpStatusCode; // +0x20C
std::mutex m_mutex;
};
using OrbisHttpsCaList = Libraries::Ssl::OrbisSslCaList;
int PS4_SYSV_ABI sceHttpAbortRequest();
@ -32,7 +39,7 @@ int PS4_SYSV_ABI sceHttpAbortRequestForce();
int PS4_SYSV_ABI sceHttpAbortWaitRequest();
int PS4_SYSV_ABI sceHttpAddCookie();
int PS4_SYSV_ABI sceHttpAddQuery();
int PS4_SYSV_ABI sceHttpAddRequestHeader();
int PS4_SYSV_ABI sceHttpAddRequestHeader(int id, const char* name, const char* value, s32 mode);
int PS4_SYSV_ABI sceHttpAddRequestHeaderRaw();
int PS4_SYSV_ABI sceHttpAuthCacheExport();
int PS4_SYSV_ABI sceHttpAuthCacheFlush();
@ -42,11 +49,12 @@ int PS4_SYSV_ABI sceHttpCookieExport();
int PS4_SYSV_ABI sceHttpCookieFlush();
int PS4_SYSV_ABI sceHttpCookieImport();
int PS4_SYSV_ABI sceHttpCreateConnection();
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL();
int PS4_SYSV_ABI sceHttpCreateConnectionWithURL(int tmplId, const char* url, bool enableKeepalive);
int PS4_SYSV_ABI sceHttpCreateEpoll();
int PS4_SYSV_ABI sceHttpCreateRequest();
int PS4_SYSV_ABI sceHttpCreateRequest2();
int PS4_SYSV_ABI sceHttpCreateRequestWithURL();
int PS4_SYSV_ABI sceHttpCreateRequestWithURL(int connId, s32 method, const char* url,
u64 contentLength);
int PS4_SYSV_ABI sceHttpCreateRequestWithURL2();
int PS4_SYSV_ABI sceHttpCreateTemplate();
int PS4_SYSV_ABI sceHttpDbgEnableProfile();
@ -62,7 +70,7 @@ int PS4_SYSV_ABI sceHttpDeleteRequest();
int PS4_SYSV_ABI sceHttpDeleteTemplate();
int PS4_SYSV_ABI sceHttpDestroyEpoll();
int PS4_SYSV_ABI sceHttpGetAcceptEncodingGZIPEnabled();
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders();
int PS4_SYSV_ABI sceHttpGetAllResponseHeaders(int reqId, char** header, u64* headerSize);
int PS4_SYSV_ABI sceHttpGetAuthEnabled();
int PS4_SYSV_ABI sceHttpGetAutoRedirect();
int PS4_SYSV_ABI sceHttpGetConnectionStat();
@ -76,10 +84,13 @@ int PS4_SYSV_ABI sceHttpGetMemoryPoolStats();
int PS4_SYSV_ABI sceHttpGetNonblock();
int PS4_SYSV_ABI sceHttpGetRegisteredCtxIds();
int PS4_SYSV_ABI sceHttpGetResponseContentLength();
int PS4_SYSV_ABI sceHttpGetStatusCode();
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, std::size_t poolSize);
int PS4_SYSV_ABI sceHttpParseResponseHeader();
int PS4_SYSV_ABI sceHttpParseStatusLine();
int PS4_SYSV_ABI sceHttpGetStatusCode(int reqId, int* statusCode);
int PS4_SYSV_ABI sceHttpInit(int libnetMemId, int libsslCtxId, u64 poolSize);
int PS4_SYSV_ABI sceHttpParseResponseHeader(const char* header, u64 headerLen, const char* fieldStr,
const char** fieldValue, u64* valueLen);
int PS4_SYSV_ABI sceHttpParseStatusLine(const char* statusLine, u64 lineLen, int32_t* httpMajorVer,
int32_t* httpMinorVer, int32_t* responseCode,
const char** reasonPhrase, u64* phraseLen);
int PS4_SYSV_ABI sceHttpReadData();
int PS4_SYSV_ABI sceHttpRedirectCacheFlush();
int PS4_SYSV_ABI sceHttpRemoveRequestHeader();
@ -88,7 +99,7 @@ int PS4_SYSV_ABI sceHttpsDisableOption();
int PS4_SYSV_ABI sceHttpsDisableOptionPrivate();
int PS4_SYSV_ABI sceHttpsEnableOption();
int PS4_SYSV_ABI sceHttpsEnableOptionPrivate();
int PS4_SYSV_ABI sceHttpSendRequest();
int PS4_SYSV_ABI sceHttpSendRequest(int reqId, const void* postData, u64 size);
int PS4_SYSV_ABI sceHttpSetAcceptEncodingGZIPEnabled();
int PS4_SYSV_ABI sceHttpSetAuthEnabled();
int PS4_SYSV_ABI sceHttpSetAuthInfoCallback();
@ -134,14 +145,16 @@ int PS4_SYSV_ABI sceHttpTerm();
int PS4_SYSV_ABI sceHttpTryGetNonblock();
int PS4_SYSV_ABI sceHttpTrySetNonblock();
int PS4_SYSV_ABI sceHttpUnsetEpoll();
int PS4_SYSV_ABI sceHttpUriBuild();
int PS4_SYSV_ABI sceHttpUriBuild(char* out, u64* require, u64 prepare,
const OrbisHttpUriElement* srcElement, u32 option);
int PS4_SYSV_ABI sceHttpUriCopy();
int PS4_SYSV_ABI sceHttpUriEscape();
int PS4_SYSV_ABI sceHttpUriMerge();
int PS4_SYSV_ABI sceHttpUriMerge(char* mergedUrl, char* url, char* relativeUri, u64* require,
u64 prepare, u32 option);
int PS4_SYSV_ABI sceHttpUriParse(OrbisHttpUriElement* out, const char* srcUri, void* pool,
size_t* require, size_t prepare);
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, size_t srcSize);
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, size_t* require, size_t prepare, const char* in);
u64* require, u64 prepare);
int PS4_SYSV_ABI sceHttpUriSweepPath(char* dst, const char* src, u64 srcSize);
int PS4_SYSV_ABI sceHttpUriUnescape(char* out, u64* require, u64 prepare, const char* in);
int PS4_SYSV_ABI sceHttpWaitRequest();
void RegisterLib(Core::Loader::SymbolsResolver* sym);

View File

@ -1285,7 +1285,8 @@ u16 PS4_SYSV_ABI sceNetNtohs(u16 net16) {
int PS4_SYSV_ABI sceNetPoolCreate(const char* name, int size, int flags) {
LOG_ERROR(Lib_Net, "(DUMMY) name = {} size = {} flags = {} ", std::string(name), size, flags);
return ORBIS_OK;
static s32 id = 1;
return id++;
}
int PS4_SYSV_ABI sceNetPoolDestroy() {

View File

@ -5,7 +5,6 @@
#include <filesystem>
#include <fstream>
#include <mutex>
#include <SDL3/SDL_audio.h>
#include <cmrc/cmrc.hpp>
#include <imgui.h>
@ -92,59 +91,45 @@ TrophyUI::TrophyUI(const std::filesystem::path& trophyIconPath, const std::strin
AddLayer(this);
bool customsoundplayed = false;
#ifdef ENABLE_QT_GUI
QString musicPathWav = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.wav");
QString musicPathMp3 = QString::fromStdString(CustomTrophy_Dir.string() + "/trophy.mp3");
if (fs::exists(musicPathWav.toStdString())) {
BackgroundMusicPlayer::getInstance().setVolume(100);
BackgroundMusicPlayer::getInstance().playMusic(musicPathWav, false);
customsoundplayed = true;
} else if (fs::exists(musicPathMp3.toStdString())) {
BackgroundMusicPlayer::getInstance().setVolume(100);
BackgroundMusicPlayer::getInstance().playMusic(musicPathMp3, false);
customsoundplayed = true;
MIX_Init();
mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
if (!mixer) {
LOG_ERROR(Lib_NpTrophy, "Could not initialize SDL Mixer, {}", SDL_GetError());
return;
}
#endif
if (!customsoundplayed) {
MIX_SetMasterGain(mixer, static_cast<float>(Config::getVolumeSlider() / 100.f));
auto musicPathMp3 = CustomTrophy_Dir / "trophy.mp3";
auto musicPathWav = CustomTrophy_Dir / "trophy.wav";
if (std::filesystem::exists(musicPathMp3)) {
audio = MIX_LoadAudio(mixer, musicPathMp3.string().c_str(), false);
} else if (std::filesystem::exists(musicPathWav)) {
audio = MIX_LoadAudio(mixer, musicPathWav.string().c_str(), false);
} else {
auto soundFile = resource.open("src/images/trophy.wav");
std::vector<u8> soundData = std::vector<u8>(soundFile.begin(), soundFile.end());
audio =
MIX_LoadAudio_IO(mixer, SDL_IOFromMem(soundData.data(), soundData.size()), false, true);
// due to low volume of default sound file
MIX_SetMasterGain(mixer, MIX_GetMasterGain(mixer) * 1.3f);
}
SDL_AudioSpec spec;
Uint8* audioBuf;
Uint32 audioLen;
if (!audio) {
LOG_ERROR(Lib_NpTrophy, "Could not loud audio file, {}", SDL_GetError());
return;
}
if (!SDL_LoadWAV_IO(SDL_IOFromMem(soundData.data(), soundData.size()), true, &spec,
&audioBuf, &audioLen)) {
LOG_ERROR(Lib_NpTrophy, "Cannot load trophy sound: {}", SDL_GetError());
SDL_free(audioBuf);
return;
}
SDL_AudioStream* stream =
SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr);
if (!stream) {
LOG_ERROR(Lib_NpTrophy, "Cannot create audio stream for trophy sound: {}",
SDL_GetError());
SDL_free(audioBuf);
return;
}
if (!SDL_PutAudioStreamData(stream, audioBuf, audioLen)) {
LOG_ERROR(Lib_NpTrophy, "Cannot add trophy sound data to stream: {}", SDL_GetError());
SDL_free(audioBuf);
return;
}
// Set audio gain 20% higher since audio file itself is soft
SDL_SetAudioStreamGain(stream, Config::getVolumeSlider() / 100.0f * 1.2f);
SDL_ResumeAudioStreamDevice(stream);
SDL_free(audioBuf);
if (!MIX_PlayAudio(mixer, audio)) {
LOG_ERROR(Lib_NpTrophy, "Could not play audio file, {}", SDL_GetError());
}
}
TrophyUI::~TrophyUI() {
MIX_DestroyAudio(audio);
MIX_DestroyMixer(mixer);
MIX_Quit();
Finish();
}

View File

@ -5,6 +5,7 @@
#include <string>
#include <variant>
#include <SDL3_mixer/SDL_mixer.h>
#include <queue>
#include "common/fixed_value.h"
@ -30,6 +31,9 @@ private:
std::string_view trophy_type;
ImGui::RefCountedTexture trophy_icon;
ImGui::RefCountedTexture trophy_type_icon;
MIX_Mixer* mixer;
MIX_Audio* audio;
};
struct TrophyInfo {

View File

@ -180,7 +180,7 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
}
if (!ignore_corrupt && !read_only) {
Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Write);
Common::FS::IOFile f(corrupt_file_path, Common::FS::FileAccessMode::Create);
f.Close();
}

View File

@ -59,7 +59,7 @@ void PersistMemory(u32 slot_id, bool lock) {
while (n++ < 10) {
try {
IOFile f;
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Write);
int r = f.Open(memoryPath, Common::FS::FileAccessMode::Create);
if (f.IsOpen()) {
f.WriteRaw<u8>(data.memory_cache.data(), data.memory_cache.size());
f.Close();
@ -148,7 +148,7 @@ void SetIcon(u32 slot_id, void* buf, size_t buf_size) {
fs::copy_file(src_icon, icon_path);
}
} else {
IOFile file(icon_path, Common::FS::FileAccessMode::Write);
IOFile file(icon_path, Common::FS::FileAccessMode::Create);
file.WriteRaw<u8>(buf, buf_size);
file.Close();
}

View File

@ -1389,7 +1389,7 @@ Error PS4_SYSV_ABI sceSaveDataSaveIcon(const OrbisSaveDataMountPoint* mountPoint
}
try {
const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Write);
const Common::FS::IOFile file(path, Common::FS::FileAccessMode::Create);
file.WriteRaw<u8>(icon->buf, std::min(icon->bufSize, icon->dataSize));
} catch (const fs::filesystem_error& e) {
LOG_ERROR(Lib_SaveData, "Failed to load icon: {}", e.what());

View File

@ -0,0 +1,651 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "dimensions.h"
#include <mutex>
#include <thread>
namespace Libraries::Usbd {
static constexpr std::array<u8, 16> COMMAND_KEY = {0x55, 0xFE, 0xF6, 0xB0, 0x62, 0xBF, 0x0B, 0x41,
0xC9, 0xB3, 0x7C, 0xB4, 0x97, 0x3E, 0x29, 0x7B};
static constexpr std::array<u8, 17> CHAR_CONSTANT = {0xB7, 0xD5, 0xD7, 0xE6, 0xE7, 0xBA,
0x3C, 0xA8, 0xD8, 0x75, 0x47, 0x68,
0xCF, 0x23, 0xE9, 0xFE, 0xAA};
static constexpr std::array<u8, 25> PWD_CONSTANT = {
0x28, 0x63, 0x29, 0x20, 0x43, 0x6F, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74,
0x20, 0x4C, 0x45, 0x47, 0x4F, 0x20, 0x32, 0x30, 0x31, 0x34, 0xAA, 0xAA};
DimensionsToypad::DimensionsToypad() {}
void DimensionsToypad::LoadFigure(std::string file_name, u8 pad, u8 index) {
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
std::array<u8, 0x2D * 0x04> data;
ASSERT(file.Read(data) == data.size());
LoadDimensionsFigure(data, std::move(file), pad, index);
}
u32 DimensionsToypad::LoadDimensionsFigure(const std::array<u8, 0x2D * 0x04>& buf,
Common::FS::IOFile file, u8 pad, u8 index) {
std::lock_guard lock(m_dimensions_mutex);
const u32 id = GetFigureId(buf);
DimensionsFigure& figure = GetFigureByIndex(index);
figure.dimFile = std::move(file);
figure.id = id;
figure.pad = pad;
figure.index = index + 1;
figure.data = buf;
// When a figure is added to the toypad, respond to the game with the pad they were added to,
// their index, the direction (0x00 in byte 6 for added) and their UID
std::array<u8, 32> figureChangeResponse = {0x56, 0x0b, figure.pad, 0x00, figure.index,
0x00, buf[0], buf[1], buf[2], buf[4],
buf[5], buf[6], buf[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
return id;
}
void DimensionsToypad::RemoveFigure(u8 pad, u8 index, bool fullRemove) {
std::lock_guard lock(m_dimensions_mutex);
DimensionsFigure& figure = GetFigureByIndex(index);
if (figure.index == 255)
return;
// When a figure is removed from the toypad, respond to the game with the pad they were removed
// from, their index, the direction (0x01 in byte 6 for removed) and their UID
if (fullRemove) {
std::array<u8, 32> figureChangeResponse = {
0x56, 0x0b, figure.pad, 0x00, figure.index,
0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
figure.Save();
figure.dimFile.Close();
}
figure.index = 255;
figure.pad = 255;
figure.id = 0;
}
void DimensionsToypad::MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) {
if (old_index == new_index) {
// Don't bother removing and loading again, just send response to the game
CancelRemoveFigure(new_index);
return;
}
// When moving figures between spaces on the toypad, remove any figure from the space they are
// moving to, then remove them from their current space, then load them to the space they are
// moving to
RemoveFigure(new_pad, new_index, true);
DimensionsFigure& figure = GetFigureByIndex(old_index);
const std::array<u8, 0x2D * 0x04> data = figure.data;
Common::FS::IOFile inFile = std::move(figure.dimFile);
RemoveFigure(old_pad, old_index, false);
LoadDimensionsFigure(data, std::move(inFile), new_pad, new_index);
}
void DimensionsToypad::TempRemoveFigure(u8 index) {
std::lock_guard lock(m_dimensions_mutex);
DimensionsFigure& figure = GetFigureByIndex(index);
if (figure.index == 255)
return;
// Send a response to the game that the figure has been "Picked up" from existing slot,
// until either the movement is cancelled, or user chooses a space to move to
std::array<u8, 32> figureChangeResponse = {
0x56, 0x0b, figure.pad, 0x00, figure.index,
0x01, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
}
void DimensionsToypad::CancelRemoveFigure(u8 index) {
std::lock_guard lock(m_dimensions_mutex);
DimensionsFigure& figure = GetFigureByIndex(index);
if (figure.index == 255)
return;
// Cancel the previous movement of the figure
std::array<u8, 32> figureChangeResponse = {
0x56, 0x0b, figure.pad, 0x00, figure.index,
0x00, figure.data[0], figure.data[1], figure.data[2], figure.data[4],
figure.data[5], figure.data[6], figure.data[7]};
figureChangeResponse[13] = GenerateChecksum(figureChangeResponse, 13);
m_figure_added_removed_responses.push(figureChangeResponse);
}
u8 DimensionsToypad::GenerateChecksum(const std::array<u8, 32>& data, u32 num_of_bytes) {
int checksum = 0;
ASSERT(num_of_bytes <= data.size());
for (u8 i = 0; i < num_of_bytes; i++) {
checksum += data[i];
}
return (checksum & 0xFF);
}
void DimensionsToypad::GetBlankResponse(u8 type, u8 sequence, std::array<u8, 32>& reply_buf) {
reply_buf[0] = 0x55;
reply_buf[1] = type;
reply_buf[2] = sequence;
reply_buf[3] = GenerateChecksum(reply_buf, 3);
}
void DimensionsToypad::GenerateRandomNumber(const u8* buf, u8 sequence,
std::array<u8, 32>& reply_buf) {
// Decrypt payload into an 8 byte array
const std::array<u8, 8> value = Decrypt(buf, std::nullopt);
// Seed is the first 4 bytes (little endian) of the decrypted payload
const u32 seed = (u32&)value[0];
// Confirmation is the second 4 bytes (big endian) of the decrypted payload
// const u32 conf = (u32be&)value[4];
// Initialize rng using the seed from decrypted payload
InitializeRNG(seed);
// Encrypt 8 bytes, first 4 bytes is the decrypted confirmation from payload, 2nd 4 bytes are
// blank
std::array<u8, 8> value_to_encrypt = {value[4], value[5], value[6], value[7], 0, 0, 0, 0};
const std::array<u8, 8> encrypted = Encrypt(value_to_encrypt.data(), std::nullopt);
reply_buf[0] = 0x55;
reply_buf[1] = 0x09;
reply_buf[2] = sequence;
// Copy encrypted value to response data
std::memcpy(&reply_buf[3], encrypted.data(), encrypted.size());
reply_buf[11] = GenerateChecksum(reply_buf, 11);
}
void DimensionsToypad::InitializeRNG(u32 seed) {
m_random_a = 0xF1EA5EED;
m_random_b = seed;
m_random_c = seed;
m_random_d = seed;
for (int i = 0; i < 42; i++) {
GetNext();
}
}
u32 DimensionsToypad::GetNext() {
const u32 e = m_random_a - std::rotl(m_random_b, 21);
m_random_a = m_random_b ^ std::rotl(m_random_c, 19);
m_random_b = m_random_c + std::rotl(m_random_d, 6);
m_random_c = m_random_d + e;
m_random_d = e + m_random_a;
return m_random_d;
}
std::array<u8, 8> DimensionsToypad::Decrypt(const u8* buf, std::optional<std::array<u8, 16>> key) {
// Value to decrypt is separated in to two little endian 32 bit unsigned integers
u32 data_one = u32(buf[0]) | (u32(buf[1]) << 8) | (u32(buf[2]) << 16) | (u32(buf[3]) << 24);
u32 data_two = u32(buf[4]) | (u32(buf[5]) << 8) | (u32(buf[6]) << 16) | (u32(buf[7]) << 24);
// Use the key as 4 32 bit little endian unsigned integers
u32 key_one;
u32 key_two;
u32 key_three;
u32 key_four;
if (key) {
key_one = u32(key.value()[0]) | (u32(key.value()[1]) << 8) | (u32(key.value()[2]) << 16) |
(u32(key.value()[3]) << 24);
key_two = u32(key.value()[4]) | (u32(key.value()[5]) << 8) | (u32(key.value()[6]) << 16) |
(u32(key.value()[7]) << 24);
key_three = u32(key.value()[8]) | (u32(key.value()[9]) << 8) |
(u32(key.value()[10]) << 16) | (u32(key.value()[11]) << 24);
key_four = u32(key.value()[12]) | (u32(key.value()[13]) << 8) |
(u32(key.value()[14]) << 16) | (u32(key.value()[15]) << 24);
} else {
key_one = u32(COMMAND_KEY[0]) | (u32(COMMAND_KEY[1]) << 8) | (u32(COMMAND_KEY[2]) << 16) |
(u32(COMMAND_KEY[3]) << 24);
key_two = u32(COMMAND_KEY[4]) | (u32(COMMAND_KEY[5]) << 8) | (u32(COMMAND_KEY[6]) << 16) |
(u32(COMMAND_KEY[7]) << 24);
key_three = u32(COMMAND_KEY[8]) | (u32(COMMAND_KEY[9]) << 8) |
(u32(COMMAND_KEY[10]) << 16) | (u32(COMMAND_KEY[11]) << 24);
key_four = u32(COMMAND_KEY[12]) | (u32(COMMAND_KEY[13]) << 8) |
(u32(COMMAND_KEY[14]) << 16) | (u32(COMMAND_KEY[15]) << 24);
}
u32 sum = 0xC6EF3720;
constexpr u32 delta = 0x9E3779B9;
for (int i = 0; i < 32; i++) {
data_two -=
(((data_one << 4) + key_three) ^ (data_one + sum) ^ ((data_one >> 5) + key_four));
data_one -= (((data_two << 4) + key_one) ^ (data_two + sum) ^ ((data_two >> 5) + key_two));
sum -= delta;
}
ASSERT_MSG(sum == 0, "Decryption failed, sum inequal to 0");
std::array<u8, 8> decrypted = {u8(data_one & 0xFF), u8((data_one >> 8) & 0xFF),
u8((data_one >> 16) & 0xFF), u8((data_one >> 24) & 0xFF),
u8(data_two & 0xFF), u8((data_two >> 8) & 0xFF),
u8((data_two >> 16) & 0xFF), u8((data_two >> 24) & 0xFF)};
return decrypted;
}
std::array<u8, 8> DimensionsToypad::Encrypt(const u8* buf, std::optional<std::array<u8, 16>> key) {
// Value to encrypt is separated in to two little endian 32 bit unsigned integers
u32 data_one = u32(buf[0]) | (u32(buf[1]) << 8) | (u32(buf[2]) << 16) | (u32(buf[3]) << 24);
u32 data_two = u32(buf[4]) | (u32(buf[5]) << 8) | (u32(buf[6]) << 16) | (u32(buf[7]) << 24);
// Use the key as 4 32 bit little endian unsigned integers
u32 key_one;
u32 key_two;
u32 key_three;
u32 key_four;
if (key) {
key_one = u32(key.value()[0]) | (u32(key.value()[1]) << 8) | (u32(key.value()[2]) << 16) |
(u32(key.value()[3]) << 24);
key_two = u32(key.value()[4]) | (u32(key.value()[5]) << 8) | (u32(key.value()[6]) << 16) |
(u32(key.value()[7]) << 24);
key_three = u32(key.value()[8]) | (u32(key.value()[9]) << 8) |
(u32(key.value()[10]) << 16) | (u32(key.value()[11]) << 24);
key_four = u32(key.value()[12]) | (u32(key.value()[13]) << 8) |
(u32(key.value()[14]) << 16) | (u32(key.value()[15]) << 24);
} else {
key_one = u32(COMMAND_KEY[0]) | (u32(COMMAND_KEY[1]) << 8) | (u32(COMMAND_KEY[2]) << 16) |
(u32(COMMAND_KEY[3]) << 24);
key_two = u32(COMMAND_KEY[4]) | (u32(COMMAND_KEY[5]) << 8) | (u32(COMMAND_KEY[6]) << 16) |
(u32(COMMAND_KEY[7]) << 24);
key_three = u32(COMMAND_KEY[8]) | (u32(COMMAND_KEY[9]) << 8) |
(u32(COMMAND_KEY[10]) << 16) | (u32(COMMAND_KEY[11]) << 24);
key_four = u32(COMMAND_KEY[12]) | (u32(COMMAND_KEY[13]) << 8) |
(u32(COMMAND_KEY[14]) << 16) | (u32(COMMAND_KEY[15]) << 24);
}
u32 sum = 0;
u32 delta = 0x9E3779B9;
for (int i = 0; i < 32; i++) {
sum += delta;
data_one += (((data_two << 4) + key_one) ^ (data_two + sum) ^ ((data_two >> 5) + key_two));
data_two +=
(((data_one << 4) + key_three) ^ (data_one + sum) ^ ((data_one >> 5) + key_four));
}
std::array<u8, 8> encrypted = {u8(data_one & 0xFF), u8((data_one >> 8) & 0xFF),
u8((data_one >> 16) & 0xFF), u8((data_one >> 24) & 0xFF),
u8(data_two & 0xFF), u8((data_two >> 8) & 0xFF),
u8((data_two >> 16) & 0xFF), u8((data_two >> 24) & 0xFF)};
return encrypted;
}
std::array<u8, 16> DimensionsToypad::GenerateFigureKey(const std::array<u8, 0x2D * 0x04>& buf) {
std::array<u8, 7> uid = {buf[0], buf[1], buf[2], buf[4], buf[5], buf[6], buf[7]};
u32 scrambleA = Scramble(uid, 3);
u32 scrambleB = Scramble(uid, 4);
u32 scrambleC = Scramble(uid, 5);
u32 scrambleD = Scramble(uid, 6);
return {
u8((scrambleA >> 24) & 0xFF), u8((scrambleA >> 16) & 0xFF), u8((scrambleA >> 8) & 0xFF),
u8(scrambleA & 0xFF), u8((scrambleB >> 24) & 0xFF), u8((scrambleB >> 16) & 0xFF),
u8((scrambleB >> 8) & 0xFF), u8(scrambleB & 0xFF), u8((scrambleC >> 24) & 0xFF),
u8((scrambleC >> 16) & 0xFF), u8((scrambleC >> 8) & 0xFF), u8(scrambleC & 0xFF),
u8((scrambleD >> 24) & 0xFF), u8((scrambleD >> 16) & 0xFF), u8((scrambleD >> 8) & 0xFF),
u8(scrambleD & 0xFF)};
}
u32 DimensionsToypad::Scramble(const std::array<u8, 7>& uid, u8 count) {
std::vector<u8> to_scramble;
to_scramble.reserve(uid.size() + CHAR_CONSTANT.size());
for (u8 x : uid) {
to_scramble.push_back(x);
}
for (u8 c : CHAR_CONSTANT) {
to_scramble.push_back(c);
}
to_scramble[(count * 4) - 1] = 0xaa;
std::array<u8, 4> randomized = DimensionsRandomize(to_scramble, count);
return (u32(randomized[0]) << 24) | (u32(randomized[1]) << 16) | (u32(randomized[2]) << 8) |
u32(randomized[3]);
}
std::array<u8, 4> DimensionsToypad::PWDGenerate(const std::array<u8, 7>& uid) {
std::vector<u8> pwdCalc = {PWD_CONSTANT.begin(), PWD_CONSTANT.end() - 1};
for (u8 i = 0; i < uid.size(); i++) {
pwdCalc.insert(pwdCalc.begin() + i, uid[i]);
}
return DimensionsRandomize(pwdCalc, 8);
}
std::array<u8, 4> DimensionsToypad::DimensionsRandomize(const std::vector<u8>& key, u8 count) {
u32 scrambled = 0;
for (u8 i = 0; i < count; i++) {
const u32 v4 = std::rotr(scrambled, 25);
const u32 v5 = std::rotr(scrambled, 10);
const u32 b = u32(key[i * 4]) | (u32(key[(i * 4) + 1]) << 8) |
(u32(key[(i * 4) + 2]) << 16) | (u32(key[(i * 4) + 3]) << 24);
scrambled = b + v4 + v5 - scrambled;
}
return {u8(scrambled & 0xFF), u8(scrambled >> 8 & 0xFF), u8(scrambled >> 16 & 0xFF),
u8(scrambled >> 24 & 0xFF)};
}
u32 DimensionsToypad::GetFigureId(const std::array<u8, 0x2D * 0x04>& buf) {
const std::array<u8, 16> figure_key = GenerateFigureKey(buf);
const std::array<u8, 8> decrypted = Decrypt(&buf[36 * 4], figure_key);
const u32 fig_num = u32(decrypted[0]) | (u32(decrypted[1]) << 8) | (u32(decrypted[2]) << 16) |
(u32(decrypted[3]) << 24);
// Characters have their model number encrypted in page 36
if (fig_num < 1000) {
return fig_num;
}
// Vehicles/Gadgets have their model number written as little endian in page 36
return u32(buf[36 * 4]) | (u32(buf[(36 * 4) + 1]) << 8) | (u32(buf[(36 * 4) + 2]) << 16) |
(u32(buf[(36 * 4) + 3]) << 24);
}
DimensionsFigure& DimensionsToypad::GetFigureByIndex(u8 index) {
return m_figures[index];
}
void DimensionsToypad::RandomUID(u8* uid_buffer) {
uid_buffer[0] = 0x04;
uid_buffer[7] = 0x80;
for (u8 i = 1; i < 7; i++) {
u8 random = rand() % 255;
uid_buffer[i] = random;
}
}
void DimensionsToypad::GetChallengeResponse(const u8* buf, u8 sequence,
std::array<u8, 32>& reply_buf) {
// Decrypt payload into an 8 byte array
const std::array<u8, 8> value = Decrypt(buf, std::nullopt);
// Confirmation is the first 4 bytes of the decrypted payload
// const u32 conf = read_from_ptr<be_t<u32>>(value);
// Generate next random number based on RNG
const u32 next_random = GetNext();
// Encrypt an 8 byte array, first 4 bytes are the next random number (little endian)
// followed by the confirmation from the decrypted payload
std::array<u8, 8> value_to_encrypt = {u8(next_random & 0xFF),
u8((next_random >> 8) & 0xFF),
u8((next_random >> 16) & 0xFF),
u8((next_random >> 24) & 0xFF),
value[0],
value[1],
value[2],
value[3]};
const std::array<u8, 8> encrypted = Encrypt(value_to_encrypt.data(), std::nullopt);
reply_buf[0] = 0x55;
reply_buf[1] = 0x09;
reply_buf[2] = sequence;
// Copy encrypted value to response data
std::memcpy(&reply_buf[3], encrypted.data(), encrypted.size());
reply_buf[11] = GenerateChecksum(reply_buf, 11);
}
void DimensionsToypad::QueryBlock(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(m_dimensions_mutex);
reply_buf[0] = 0x55;
reply_buf[1] = 0x12;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// Index from game begins at 1 rather than 0, so minus 1 here
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
const DimensionsFigure& figure = GetFigureByIndex(figure_index);
// Query 4 pages of 4 bytes from the figure, copy this to the response
if (figure.index != 255 && (4 * page) < ((0x2D * 4) - 16)) {
std::memcpy(&reply_buf[4], figure.data.data() + (4 * page), 16);
}
}
reply_buf[20] = GenerateChecksum(reply_buf, 20);
}
void DimensionsToypad::WriteBlock(u8 index, u8 page, const u8* to_write_buf,
std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(m_dimensions_mutex);
reply_buf[0] = 0x55;
reply_buf[1] = 0x02;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// Index from game begins at 1 rather than 0, so minus 1 here
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
DimensionsFigure& figure = GetFigureByIndex(figure_index);
// Copy 4 bytes to the page on the figure requested by the game
if (figure.index != 255 && page < 0x2D) {
// Id is written to page 36
if (page == 36) {
figure.id = u32(to_write_buf[0]) | (u32(to_write_buf[1]) << 8) |
(u32(to_write_buf[2]) << 16) | (u32(to_write_buf[3]) << 24);
}
std::memcpy(figure.data.data() + (page * 4), to_write_buf, 4);
figure.Save();
}
}
reply_buf[4] = GenerateChecksum(reply_buf, 4);
}
void DimensionsToypad::GetModel(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf) {
// Decrypt payload to 8 byte array, byte 1 is the index, 4-7 are the confirmation
const std::array<u8, 8> value = Decrypt(buf, std::nullopt);
const u8 index = value[0];
// const u32 conf = read_from_ptr<be_t<u32>>(value, 4);
std::array<u8, 8> value_to_encrypt = {};
// Response is the figure's id (little endian) followed by the confirmation from payload
// Index from game begins at 1 rather than 0, so minus 1 here
if (const u8 figure_index = index - 1; figure_index < MAX_DIMENSIONS_FIGURES) {
const DimensionsFigure& figure = GetFigureByIndex(figure_index);
value_to_encrypt = {u8(figure.id & 0xFF),
u8((figure.id >> 8) & 0xFF),
u8((figure.id >> 16) & 0xFF),
u8((figure.id >> 24) & 0xFF),
value[4],
value[5],
value[6],
value[7]};
}
const std::array<u8, 8> encrypted = Encrypt(value_to_encrypt.data(), std::nullopt);
reply_buf[0] = 0x55;
reply_buf[1] = 0x0a;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
// Copy encrypted message to response
std::memcpy(&reply_buf[4], encrypted.data(), encrypted.size());
reply_buf[12] = GenerateChecksum(reply_buf, 12);
}
std::optional<std::array<u8, 32>> DimensionsToypad::PopAddedRemovedResponse() {
std::lock_guard lock(m_dimensions_mutex);
if (m_figure_added_removed_responses.empty()) {
return std::nullopt;
}
std::array<u8, 32> response = m_figure_added_removed_responses.front();
m_figure_added_removed_responses.pop();
return response;
}
libusb_endpoint_descriptor* DimensionsBackend::FillEndpointDescriptorPair() {
return m_endpoint_descriptors.data();
}
libusb_interface_descriptor* DimensionsBackend::FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) {
m_interface_descriptors[0].endpoint = descs;
return m_interface_descriptors.data();
}
libusb_config_descriptor* DimensionsBackend::FillConfigDescriptor(libusb_interface* inter) {
m_config_descriptors[0].interface = inter;
return m_config_descriptors.data();
}
libusb_device_descriptor* DimensionsBackend::FillDeviceDescriptor() {
return m_device_descriptors.data();
}
libusb_transfer_status DimensionsBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
ASSERT(transfer->length == 32);
switch (transfer->endpoint) {
case 0x81: {
// Read Endpoint, wait to respond with either an added/removed figure response, or a queued
// response from a previous write
bool responded = false;
while (!responded) {
std::lock_guard lock(m_query_mutex);
std::optional<std::array<u8, 32>> response =
m_dimensions_toypad->PopAddedRemovedResponse();
if (response) {
std::memcpy(transfer->buffer, response.value().data(), 0x20);
transfer->length = 32;
responded = true;
} else if (!m_queries.empty()) {
std::memcpy(transfer->buffer, m_queries.front().data(), 0x20);
transfer->length = 32;
m_queries.pop();
responded = true;
}
}
break;
}
case 0x01: {
// Write endpoint, similar structure of request to the Infinity Base with a command for byte
// 3, sequence for byte 4, the payload after that, then a checksum for the final byte.
const u8 command = transfer->buffer[2];
const u8 sequence = transfer->buffer[3];
std::array<u8, 32> q_result{};
switch (command) {
case 0xB0: // Wake
{
// Consistent device response to the wake command
q_result = {0x55, 0x0e, 0x01, 0x28, 0x63, 0x29, 0x20, 0x4c, 0x45,
0x47, 0x4f, 0x20, 0x32, 0x30, 0x31, 0x34, 0x46};
break;
}
case 0xB1: // Seed
{
// Initialise a random number generator using the seed provided
m_dimensions_toypad->GenerateRandomNumber(&transfer->buffer[4], sequence, q_result);
break;
}
case 0xB3: // Challenge
{
// Get the next number in the sequence based on the RNG from 0xB1 command
m_dimensions_toypad->GetChallengeResponse(&transfer->buffer[4], sequence, q_result);
break;
}
case 0xC0: // Color
case 0xC1: // Get Pad Color
case 0xC2: // Fade
case 0xC3: // Flash
case 0xC4: // Fade Random
case 0xC6: // Fade All
case 0xC7: // Flash All
case 0xC8: // Color All
{
// Send a blank response to acknowledge color has been sent to toypad
m_dimensions_toypad->GetBlankResponse(0x01, sequence, q_result);
break;
}
case 0xD2: // Read
{
// Read 4 pages from the figure at index (buf[4]), starting with page buf[5]
m_dimensions_toypad->QueryBlock(transfer->buffer[4], transfer->buffer[5], q_result,
sequence);
break;
}
case 0xD3: // Write
{
// Write 4 bytes to page buf[5] to the figure at index buf[4]
m_dimensions_toypad->WriteBlock(transfer->buffer[4], transfer->buffer[5],
&transfer->buffer[6], q_result, sequence);
break;
}
case 0xD4: // Model
{
// Get the model id of the figure at index buf[4]
m_dimensions_toypad->GetModel(&transfer->buffer[4], sequence, q_result);
break;
}
case 0xD0: // Tag List
case 0xE1: // PWD
case 0xE5: // Active
case 0xFF: // LEDS Query
{
// Further investigation required
LOG_ERROR(Lib_Usbd, "Unimplemented LD Function: {:x}", command);
break;
}
default: {
LOG_ERROR(Lib_Usbd, "Unknown LD Function: {:x}", command);
break;
}
}
std::lock_guard lock(m_query_mutex);
m_queries.push(q_result);
break;
}
default:
break;
}
return LIBUSB_TRANSFER_COMPLETED;
}
s32 DimensionsBackend::SubmitTransfer(libusb_transfer* transfer) {
if (transfer->endpoint == 0x01) {
std::thread write_thread([this, transfer] {
HandleAsyncTransfer(transfer);
const u8 flags = transfer->flags;
transfer->status = LIBUSB_TRANSFER_COMPLETED;
transfer->actual_length = transfer->length;
if (transfer->callback) {
transfer->callback(transfer);
}
if (flags & LIBUSB_TRANSFER_FREE_TRANSFER) {
libusb_free_transfer(transfer);
}
});
write_thread.detach();
return LIBUSB_SUCCESS;
}
return UsbEmulatedBackend::SubmitTransfer(transfer);
}
void DimensionsFigure::Save() {
if (!dimFile.IsOpen())
return;
dimFile.Seek(0);
dimFile.Write(data);
}
} // namespace Libraries::Usbd

View File

@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include "common/io_file.h"
#include "core/libraries/usbd/usb_backend.h"
namespace Libraries::Usbd {
constexpr u8 DIMEMSIONS_BLOCK_COUNT = 0x2D;
constexpr u8 DIMENSIONS_BLOCK_SIZE = 0x04;
constexpr u8 DIMENSIONS_FIGURE_SIZE = DIMEMSIONS_BLOCK_COUNT * DIMENSIONS_BLOCK_SIZE;
constexpr u8 MAX_DIMENSIONS_FIGURES = 7;
struct DimensionsFigure final {
Common::FS::IOFile dimFile;
std::array<u8, DIMENSIONS_FIGURE_SIZE> data{};
u8 index = 255;
u8 pad = 255;
u32 id = 0;
void Save();
};
class DimensionsToypad final : public UsbEmulatedImpl {
public:
DimensionsToypad();
~DimensionsToypad() override = default;
static void GetBlankResponse(u8 type, u8 sequence, std::array<u8, 32>& reply_buf);
void GenerateRandomNumber(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void InitializeRNG(u32 seed);
void GetChallengeResponse(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void QueryBlock(u8 index, u8 page, std::array<u8, 32>& reply_buf, u8 sequence);
void WriteBlock(u8 index, u8 page, const u8* to_write_buf, std::array<u8, 32>& reply_buf,
u8 sequence);
void GetModel(const u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
std::optional<std::array<u8, 32>> PopAddedRemovedResponse();
void LoadFigure(std::string file_name, u8 pad, u8 slot) override;
void RemoveFigure(u8 pad, u8 slot, bool full_remove) override;
void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) override;
void TempRemoveFigure(u8 index) override;
void CancelRemoveFigure(u8 index) override;
u32 LoadDimensionsFigure(const std::array<u8, 0x2D * 0x04>& buf, Common::FS::IOFile file,
u8 pad, u8 index);
protected:
std::mutex m_dimensions_mutex;
std::array<DimensionsFigure, MAX_DIMENSIONS_FIGURES> m_figures{};
private:
static void RandomUID(u8* uid_buffer);
static u8 GenerateChecksum(const std::array<u8, 32>& data, u32 num_of_bytes);
static std::array<u8, 8> Decrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
static std::array<u8, 8> Encrypt(const u8* buf, std::optional<std::array<u8, 16>> key);
static std::array<u8, 16> GenerateFigureKey(const std::array<u8, 0x2D * 0x04>& buf);
static u32 Scramble(const std::array<u8, 7>& uid, u8 count);
static std::array<u8, 4> PWDGenerate(const std::array<u8, 7>& uid);
static std::array<u8, 4> DimensionsRandomize(const std::vector<u8>& key, u8 count);
static u32 GetFigureId(const std::array<u8, 0x2D * 0x04>& buf);
u32 GetNext();
DimensionsFigure& GetFigureByIndex(u8 index);
u32 m_random_a{};
u32 m_random_b{};
u32 m_random_c{};
u32 m_random_d{};
u8 m_figure_order = 0;
std::queue<std::array<u8, 32>> m_figure_added_removed_responses;
};
class DimensionsBackend final : public UsbEmulatedBackend {
protected:
libusb_endpoint_descriptor* FillEndpointDescriptorPair() override;
libusb_interface_descriptor* FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) override;
libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) override;
libusb_device_descriptor* FillDeviceDescriptor() override;
s32 GetMaxPacketSize(libusb_device* dev, u8 endpoint) override {
return 32;
}
s32 SubmitTransfer(libusb_transfer* transfer) override;
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
return m_dimensions_toypad;
}
std::mutex m_query_mutex;
std::queue<std::array<u8, 32>> m_queries;
private:
std::shared_ptr<DimensionsToypad> m_dimensions_toypad = std::make_shared<DimensionsToypad>();
std::array<u8, 9> m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
std::vector<libusb_endpoint_descriptor> m_endpoint_descriptors = {
{0x7, 0x5, 0x81, 0x3, 0x20, 0x1, 0x0, 0x0}, {0x7, 0x5, 0x1, 0x3, 0x20, 0x1, 0x0, 0x0}};
std::vector<libusb_interface_descriptor> m_interface_descriptors = {
{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}};
std::vector<libusb_config_descriptor> m_config_descriptors = {
{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}};
std::vector<libusb_device_descriptor> m_device_descriptors = {
{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x20, 0x0E6F, 0x0241, 0x200, 0x1, 0x2, 0x0, 0x1}};
};
} // namespace Libraries::Usbd

View File

@ -0,0 +1,392 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "infinity.h"
#include <mutex>
namespace Libraries::Usbd {
InfinityBase::InfinityBase() {}
void InfinityBase::LoadFigure(std::string file_name, u8 pad, u8 slot) {
Common::FS::IOFile file(file_name, Common::FS::FileAccessMode::ReadWrite);
std::array<u8, INFINITY_FIGURE_SIZE> data;
ASSERT(file.Read(data) == data.size());
LoadInfinityFigure(data, std::move(file), slot);
}
void InfinityBase::RemoveFigure(u8 pad, u8 slot, bool full_remove) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = infinity_figures[slot];
if (!figure.present) {
return;
}
slot = DeriveFigurePosition(slot);
if (slot == 0) {
return;
}
figure.present = false;
std::array<u8, 32> figure_change_response = {0xab, 0x04, slot, 0x09, figure.order_added, 0x01};
figure_change_response[6] = GenerateChecksum(figure_change_response, 6);
m_figure_added_removed_responses.push(figure_change_response);
figure.Save();
figure.infFile.Close();
}
void InfinityBase::LoadInfinityFigure(const std::array<u8, INFINITY_FIGURE_SIZE>& buf,
Common::FS::IOFile file, u8 position) {
std::lock_guard lock(infinity_mutex);
u8 order_added;
InfinityFigure& figure = infinity_figures[position];
figure.infFile = std::move(file);
memcpy(figure.data.data(), buf.data(), figure.data.size());
figure.present = true;
if (figure.order_added == 255) {
figure.order_added = m_figure_order;
m_figure_order++;
}
order_added = figure.order_added;
position = DeriveFigurePosition(position);
if (position == 0) {
return;
}
std::array<u8, 32> figure_change_response = {0xab, 0x04, position, 0x09, order_added, 0x00};
figure_change_response[6] = GenerateChecksum(figure_change_response, 6);
m_figure_added_removed_responses.push(figure_change_response);
}
void InfinityBase::GetBlankResponse(u8 sequence, std::array<u8, 32>& reply_buf) {
reply_buf[0] = 0xaa;
reply_buf[1] = 0x01;
reply_buf[2] = sequence;
reply_buf[3] = GenerateChecksum(reply_buf, 3);
}
void InfinityBase::DescrambleAndSeed(u8* buf, u8 sequence, std::array<u8, 32>& reply_buf) {
u64 value = u64(buf[4]) << 56 | u64(buf[5]) << 48 | u64(buf[6]) << 40 | u64(buf[7]) << 32 |
u64(buf[8]) << 24 | u64(buf[9]) << 16 | u64(buf[10]) << 8 | u64(buf[11]);
u32 seed = Descramble(value);
GenerateSeed(seed);
GetBlankResponse(sequence, reply_buf);
}
void InfinityBase::GetNextAndScramble(u8 sequence, std::array<u8, 32>& reply_buf) {
const u32 next_random = GetNext();
const u64 scrambled_next_random = Scramble(next_random, 0);
reply_buf = {0xAA, 0x09, sequence};
reply_buf[3] = u8((scrambled_next_random >> 56) & 0xFF);
reply_buf[4] = u8((scrambled_next_random >> 48) & 0xFF);
reply_buf[5] = u8((scrambled_next_random >> 40) & 0xFF);
reply_buf[6] = u8((scrambled_next_random >> 32) & 0xFF);
reply_buf[7] = u8((scrambled_next_random >> 24) & 0xFF);
reply_buf[8] = u8((scrambled_next_random >> 16) & 0xFF);
reply_buf[9] = u8((scrambled_next_random >> 8) & 0xFF);
reply_buf[10] = u8(scrambled_next_random & 0xFF);
reply_buf[11] = GenerateChecksum(reply_buf, 11);
}
void InfinityBase::GetPresentFigures(u8 sequence, std::array<u8, 32>& reply_buf) {
int x = 3;
for (u8 i = 0; i < infinity_figures.size(); i++) {
u8 slot = i == 0 ? 0x10 : (i < 4) ? 0x20 : 0x30;
if (infinity_figures[i].present) {
reply_buf[x] = slot + infinity_figures[i].order_added;
reply_buf[x + 1] = 0x09;
x += 2;
}
}
reply_buf[0] = 0xaa;
reply_buf[1] = x - 2;
reply_buf[2] = sequence;
reply_buf[x] = GenerateChecksum(reply_buf, x);
}
void InfinityBase::QueryBlock(u8 fig_num, u8 block, std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = GetFigureByOrder(fig_num);
reply_buf[0] = 0xaa;
reply_buf[1] = 0x12;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
const u8 file_block = (block == 0) ? 1 : (block * 4);
if (figure.present && file_block < 20) {
memcpy(&reply_buf[4], figure.data.data() + (16 * file_block), 16);
}
reply_buf[20] = GenerateChecksum(reply_buf, 20);
}
void InfinityBase::WriteBlock(u8 fig_num, u8 block, const u8* to_write_buf,
std::array<u8, 32>& reply_buf, u8 sequence) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = GetFigureByOrder(fig_num);
reply_buf[0] = 0xaa;
reply_buf[1] = 0x02;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
const u8 file_block = (block == 0) ? 1 : (block * 4);
if (figure.present && file_block < 20) {
memcpy(figure.data.data() + (file_block * 16), to_write_buf, 16);
figure.Save();
}
reply_buf[4] = GenerateChecksum(reply_buf, 4);
}
void InfinityBase::GetFigureIdentifier(u8 fig_num, u8 sequence, std::array<u8, 32>& reply_buf) {
std::lock_guard lock(infinity_mutex);
InfinityFigure& figure = GetFigureByOrder(fig_num);
reply_buf[0] = 0xaa;
reply_buf[1] = 0x09;
reply_buf[2] = sequence;
reply_buf[3] = 0x00;
if (figure.present) {
memcpy(&reply_buf[4], figure.data.data(), 7);
}
reply_buf[11] = GenerateChecksum(reply_buf, 11);
}
std::optional<std::array<u8, 32>> InfinityBase::PopAddedRemovedResponse() {
if (m_figure_added_removed_responses.empty())
return std::nullopt;
std::array<u8, 32> response = m_figure_added_removed_responses.front();
m_figure_added_removed_responses.pop();
return response;
}
u8 InfinityBase::GenerateChecksum(const std::array<u8, 32>& data, int num_of_bytes) const {
int checksum = 0;
for (int i = 0; i < num_of_bytes; i++) {
checksum += data[i];
}
return (checksum & 0xFF);
}
u32 InfinityBase::Descramble(u64 num_to_descramble) {
u64 mask = 0x8E55AA1B3999E8AA;
u32 ret = 0;
for (int i = 0; i < 64; i++) {
if (mask & 0x8000000000000000) {
ret = (ret << 1) | (num_to_descramble & 0x01);
}
num_to_descramble >>= 1;
mask <<= 1;
}
return ret;
}
u64 InfinityBase::Scramble(u32 num_to_scramble, u32 garbage) {
u64 mask = 0x8E55AA1B3999E8AA;
u64 ret = 0;
for (int i = 0; i < 64; i++) {
ret <<= 1;
if ((mask & 1) != 0) {
ret |= (num_to_scramble & 1);
num_to_scramble >>= 1;
} else {
ret |= (garbage & 1);
garbage >>= 1;
}
mask >>= 1;
}
return ret;
}
void InfinityBase::GenerateSeed(u32 seed) {
random_a = 0xF1EA5EED;
random_b = seed;
random_c = seed;
random_d = seed;
for (int i = 0; i < 23; i++) {
GetNext();
}
}
u32 InfinityBase::GetNext() {
u32 a = random_a;
u32 b = random_b;
u32 c = random_c;
u32 ret = std::rotl(random_b, 27);
const u32 temp = (a + ((ret ^ 0xFFFFFFFF) + 1));
b ^= std::rotl(c, 17);
a = random_d;
c += a;
ret = b + temp;
a += temp;
random_c = a;
random_a = b;
random_b = c;
random_d = ret;
return ret;
}
InfinityFigure& InfinityBase::GetFigureByOrder(u8 order_added) {
for (u8 i = 0; i < infinity_figures.size(); i++) {
if (infinity_figures[i].order_added == order_added) {
return infinity_figures[i];
}
}
return infinity_figures[0];
}
u8 InfinityBase::DeriveFigurePosition(u8 position) {
switch (position) {
case 0:
case 1:
case 2:
return 1;
case 3:
case 4:
case 5:
return 2;
case 6:
case 7:
case 8:
return 3;
default:
return 0;
}
}
libusb_endpoint_descriptor* InfinityBackend::FillEndpointDescriptorPair() {
return m_endpoint_descriptors.data();
}
libusb_interface_descriptor* InfinityBackend::FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) {
m_interface_descriptors[0].endpoint = descs;
return m_interface_descriptors.data();
}
libusb_config_descriptor* InfinityBackend::FillConfigDescriptor(libusb_interface* inter) {
m_config_descriptors[0].interface = inter;
return m_config_descriptors.data();
}
libusb_device_descriptor* InfinityBackend::FillDeviceDescriptor() {
return m_device_descriptors.data();
}
libusb_transfer_status InfinityBackend::HandleAsyncTransfer(libusb_transfer* transfer) {
switch (transfer->endpoint) {
case 0x81: {
// Respond after FF command
std::optional<std::array<u8, 32>> response = m_infinity_base->PopAddedRemovedResponse();
if (response) {
memcpy(transfer->buffer, response.value().data(), 0x20);
} else if (!m_queries.empty()) {
memcpy(transfer->buffer, m_queries.front().data(), 0x20);
m_queries.pop();
}
break;
}
case 0x01: {
const u8 command = transfer->buffer[2];
const u8 sequence = transfer->buffer[3];
LOG_INFO(Lib_Usbd, "Infinity Backend Transfer command: {:x}", command);
std::array<u8, 32> q_result{};
switch (command) {
case 0x80: {
q_result = {0xaa, 0x15, 0x00, 0x00, 0x0f, 0x01, 0x00, 0x03, 0x02, 0x09, 0x09, 0x43,
0x20, 0x32, 0x62, 0x36, 0x36, 0x4b, 0x34, 0x99, 0x67, 0x31, 0x93, 0x8c};
break;
}
case 0x81: {
// Initiate Challenge
m_infinity_base->DescrambleAndSeed(transfer->buffer, sequence, q_result);
break;
}
case 0x83: {
// Challenge Response
m_infinity_base->GetNextAndScramble(sequence, q_result);
break;
}
case 0x90:
case 0x92:
case 0x93:
case 0x95:
case 0x96: {
// Color commands
m_infinity_base->GetBlankResponse(sequence, q_result);
break;
}
case 0xA1: {
// Get Present Figures
m_infinity_base->GetPresentFigures(sequence, q_result);
break;
}
case 0xA2: {
// Read Block from Figure
m_infinity_base->QueryBlock(transfer->buffer[4], transfer->buffer[5], q_result,
sequence);
break;
}
case 0xA3: {
// Write block to figure
m_infinity_base->WriteBlock(transfer->buffer[4], transfer->buffer[5],
&transfer->buffer[7], q_result, sequence);
break;
}
case 0xB4: {
// Get figure ID
m_infinity_base->GetFigureIdentifier(transfer->buffer[4], sequence, q_result);
break;
}
case 0xB5: {
// Get status?
m_infinity_base->GetBlankResponse(sequence, q_result);
break;
}
default:
LOG_ERROR(Lib_Usbd, "Unhandled Infinity Query: {}", command);
break;
}
m_queries.push(q_result);
break;
}
default:
LOG_ERROR(Lib_Usbd, "Unhandled Infinity Endpoint: {}", transfer->endpoint);
break;
}
return LIBUSB_TRANSFER_COMPLETED;
}
void InfinityFigure::Save() {
if (!infFile.IsOpen())
return;
infFile.Seek(0);
infFile.Write(data);
}
} // namespace Libraries::Usbd

View File

@ -0,0 +1,112 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include "common/io_file.h"
#include "core/libraries/usbd/usb_backend.h"
namespace Libraries::Usbd {
constexpr u16 INFINITY_BLOCK_COUNT = 0x14;
constexpr u16 INFINITY_BLOCK_SIZE = 0x10;
constexpr u16 INFINITY_FIGURE_SIZE = INFINITY_BLOCK_COUNT * INFINITY_BLOCK_SIZE;
constexpr u8 MAX_INFINITY_FIGURES = 9;
struct InfinityFigure final {
Common::FS::IOFile infFile;
std::array<u8, INFINITY_FIGURE_SIZE> data{};
bool present = false;
u8 order_added = 255;
void Save();
};
class InfinityBase final : public UsbEmulatedImpl {
public:
InfinityBase();
~InfinityBase() override = default;
void GetBlankResponse(u8 sequence, std::array<u8, 32>& reply_buf);
void DescrambleAndSeed(u8* buf, u8 sequence, std::array<u8, 32>& reply_buf);
void GetNextAndScramble(u8 sequence, std::array<u8, 32>& reply_buf);
void GetPresentFigures(u8 sequence, std::array<u8, 32>& reply_buf);
void QueryBlock(u8 fig_num, u8 block, std::array<u8, 32>& reply_buf, u8 sequence);
void WriteBlock(u8 fig_num, u8 block, const u8* to_write_buf, std::array<u8, 32>& reply_buf,
u8 sequence);
void GetFigureIdentifier(u8 fig_num, u8 sequence, std::array<u8, 32>& reply_buf);
std::optional<std::array<u8, 32>> PopAddedRemovedResponse();
void LoadFigure(std::string file_name, u8 pad, u8 slot) override;
void RemoveFigure(u8 pad, u8 slot, bool full_remove) override;
void MoveFigure(u8 new_pad, u8 new_index, u8 old_pad, u8 old_index) override {}
void TempRemoveFigure(u8 index) override {}
void CancelRemoveFigure(u8 index) override {}
void LoadInfinityFigure(const std::array<u8, 0x14 * 0x10>& buf, Common::FS::IOFile file,
u8 position);
protected:
std::mutex infinity_mutex;
std::array<InfinityFigure, MAX_INFINITY_FIGURES> infinity_figures;
private:
u8 GenerateChecksum(const std::array<u8, 32>& data, int num_of_bytes) const;
u32 Descramble(u64 num_to_descramble);
u64 Scramble(u32 num_to_scramble, u32 garbage);
void GenerateSeed(u32 seed);
u32 GetNext();
InfinityFigure& GetFigureByOrder(u8 order_added);
u8 DeriveFigurePosition(u8 position);
u32 random_a = 0;
u32 random_b = 0;
u32 random_c = 0;
u32 random_d = 0;
u8 m_figure_order = 0;
std::queue<std::array<u8, 32>> m_figure_added_removed_responses;
};
class InfinityBackend final : public UsbEmulatedBackend {
protected:
libusb_endpoint_descriptor* FillEndpointDescriptorPair() override;
libusb_interface_descriptor* FillInterfaceDescriptor(
libusb_endpoint_descriptor* descs) override;
libusb_config_descriptor* FillConfigDescriptor(libusb_interface* inter) override;
libusb_device_descriptor* FillDeviceDescriptor() override;
s32 ControlTransfer(libusb_device_handle* dev_handle, u8 bmRequestType, u8 bRequest, u16 wValue,
u16 wIndex, u8* data, u16 wLength, u32 timeout) override {
return LIBUSB_SUCCESS;
}
libusb_transfer_status HandleAsyncTransfer(libusb_transfer* transfer) override;
std::shared_ptr<UsbEmulatedImpl> GetImplRef() override {
return m_infinity_base;
}
private:
std::shared_ptr<InfinityBase> m_infinity_base = std::make_shared<InfinityBase>();
std::array<u8, 9> m_endpoint_out_extra = {0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x1d, 0x00};
std::vector<libusb_endpoint_descriptor> m_endpoint_descriptors = {
{0x7, 0x5, 0x81, 0x3, 0x20, 0x1, 0x0, 0x0},
{0x7, 0x5, 0x1, 0x3, 0x20, 0x1, 0x0, 0x0, m_endpoint_out_extra.data(), 9}};
std::vector<libusb_interface_descriptor> m_interface_descriptors = {
{0x9, 0x4, 0x0, 0x0, 0x2, 0x3, 0x0, 0x0, 0x0}};
std::vector<libusb_config_descriptor> m_config_descriptors = {
{0x9, 0x2, 0x29, 0x1, 0x1, 0x0, 0x80, 0xFA}};
std::vector<libusb_device_descriptor> m_device_descriptors = {
{0x12, 0x1, 0x200, 0x0, 0x0, 0x0, 0x20, 0x0E6F, 0x0129, 0x200, 0x1, 0x2, 0x3, 0x1}};
std::queue<std::array<u8, 32>> m_queries;
};
} // namespace Libraries::Usbd

View File

@ -312,7 +312,7 @@ public:
const auto endpoint_descs = FillEndpointDescriptorPair();
const auto interface_desc = FillInterfaceDescriptor(endpoint_descs);
const auto interface = static_cast<libusb_interface*>(calloc(1, sizeof(libusb_interface*)));
const auto interface = static_cast<libusb_interface*>(calloc(1, sizeof(libusb_interface)));
interface->altsetting = interface_desc;
interface->num_altsetting = 1;
@ -366,7 +366,7 @@ public:
const auto desc = FillDeviceDescriptor();
ASSERT(desc);
const auto fake = static_cast<UsbDevice*>(calloc(1, sizeof(UsbDevice*)));
const auto fake = static_cast<UsbDevice*>(calloc(1, sizeof(UsbDevice)));
fake->bus_number = 0;
fake->port_number = 0;
fake->device_address = 0;

View File

@ -4,6 +4,8 @@
#pragma once
#include "common/types.h"
#include "emulated/dimensions.h"
#include "emulated/infinity.h"
#include "emulated/skylander.h"
#include "usb_backend.h"
@ -33,10 +35,9 @@ using SceUsbdTransfer = libusb_transfer;
using SceUsbdControlSetup = libusb_control_setup;
using SceUsbdTransferCallback = void PS4_SYSV_ABI (*)(SceUsbdTransfer* transfer);
// TODO: implement emulated devices
using SkylandersPortalBackend = SkylanderBackend;
using InfinityBaseBackend = UsbRealBackend;
using DimensionsToypadBackend = UsbRealBackend;
using InfinityBaseBackend = InfinityBackend;
using DimensionsToypadBackend = DimensionsBackend;
enum class SceUsbdSpeed : u32 {
UNKNOWN = 0,

View File

@ -502,19 +502,19 @@ bool Elf::IsSharedLib() {
}
void Elf::ElfHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
f.WriteString(ElfHeaderStr());
}
void Elf::SelfHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
f.WriteString(SElfHeaderStr());
}
void Elf::SelfSegHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
for (u16 i = 0; i < m_self.segment_count; i++) {
f.WriteString(SELFSegHeader(i));
@ -522,7 +522,7 @@ void Elf::SelfSegHeaderDebugDump(const std::filesystem::path& file_name) {
}
void Elf::PHeaderDebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
if (m_elf_header.e_phentsize > 0) {
for (u16 i = 0; i < m_elf_header.e_phnum; i++) {

View File

@ -32,7 +32,7 @@ const SymbolRecord* SymbolsResolver::FindSymbol(const SymbolResolver& s) const {
}
void SymbolsResolver::DebugDump(const std::filesystem::path& file_name) {
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Write,
Common::FS::IOFile f{file_name, Common::FS::FileAccessMode::Create,
Common::FS::FileType::TextFile};
for (const auto& symbol : m_symbols) {
const auto ids = Common::SplitString(symbol.name, '#');

View File

@ -31,6 +31,8 @@
#include "core/file_format/trp.h"
#include "core/file_sys/fs.h"
#include "core/libraries/disc_map/disc_map.h"
#include "core/libraries/font/font.h"
#include "core/libraries/font/fontft.h"
#include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libs.h"
#include "core/libraries/ngs2/ngs2.h"
@ -111,6 +113,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
std::string id;
std::string title;
std::string app_version;
u32 sdk_version;
u32 fw_version;
Common::PSFAttributes psf_attributes{};
if (param_sfo_exists) {
@ -130,8 +133,48 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
if (const auto raw_attributes = param_sfo->GetInteger("ATTRIBUTE")) {
psf_attributes.raw = *raw_attributes;
}
// Extract sdk version from pubtool info.
std::string_view pubtool_info =
param_sfo->GetString("PUBTOOLINFO").value_or("Unknown value");
u64 sdk_ver_offset = pubtool_info.find("sdk_ver");
if (sdk_ver_offset == pubtool_info.npos) {
// Default to using firmware version if SDK version is not found.
sdk_version = fw_version;
} else {
// Increment offset to account for sdk_ver= part of string.
sdk_ver_offset += 8;
u64 sdk_ver_len = pubtool_info.find(",", sdk_ver_offset);
if (sdk_ver_len == pubtool_info.npos) {
// If there's no more commas, this is likely the last entry of pubtool info.
// Use string length instead.
sdk_ver_len = pubtool_info.size();
}
sdk_ver_len -= sdk_ver_offset;
std::string sdk_ver_string = pubtool_info.substr(sdk_ver_offset, sdk_ver_len).data();
// Number is stored in base 16.
sdk_version = std::stoi(sdk_ver_string, nullptr, 16);
}
}
auto& game_info = Common::ElfInfo::Instance();
game_info.initialized = true;
game_info.game_serial = id;
game_info.title = title;
game_info.app_ver = app_version;
game_info.firmware_ver = fw_version & 0xFFF00000;
game_info.raw_firmware_ver = fw_version;
game_info.sdk_ver = sdk_version;
game_info.psf_attributes = psf_attributes;
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
if (std::filesystem::exists(pic1_path)) {
game_info.splash_path = pic1_path;
}
game_info.game_folder = game_folder;
Config::load(Common::FS::GetUserPath(Common::FS::PathType::CustomConfigs) / (id + ".toml"),
true);
@ -194,6 +237,7 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
if (param_sfo_exists) {
LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
LOG_INFO(Loader, "Fw: {:#x} App Version: {}", fw_version, app_version);
LOG_INFO(Loader, "Compiled SDK version: {:#x}", sdk_version);
LOG_INFO(Loader, "PSVR Supported: {}", (bool)psf_attributes.support_ps_vr.Value());
LOG_INFO(Loader, "PSVR Required: {}", (bool)psf_attributes.require_ps_vr.Value());
}
@ -233,22 +277,6 @@ void Emulator::Run(std::filesystem::path file, std::vector<std::string> args,
}
}
auto& game_info = Common::ElfInfo::Instance();
game_info.initialized = true;
game_info.game_serial = id;
game_info.title = title;
game_info.app_ver = app_version;
game_info.firmware_ver = fw_version & 0xFFF00000;
game_info.raw_firmware_ver = fw_version;
game_info.psf_attributes = psf_attributes;
const auto pic1_path = mnt->GetHostPath("/app0/sce_sys/pic1.png");
if (std::filesystem::exists(pic1_path)) {
game_info.splash_path = pic1_path;
}
game_info.game_folder = game_folder;
std::string game_title = fmt::format("{} - {} <{}>", id, title, app_version);
std::string window_title = "";
std::string remote_url(Common::g_scm_remote_url);
@ -473,8 +501,8 @@ void Emulator::LoadSystemModules(const std::string& game_serial) {
{"libSceJson2.sprx", nullptr},
{"libSceLibcInternal.sprx", &Libraries::LibcInternal::RegisterLib},
{"libSceCesCs.sprx", nullptr},
{"libSceFont.sprx", nullptr},
{"libSceFontFt.sprx", nullptr},
{"libSceFont.sprx", &Libraries::Font::RegisterlibSceFont},
{"libSceFontFt.sprx", &Libraries::FontFt::RegisterlibSceFontFt},
{"libSceFreeTypeOt.sprx", nullptr}});
std::vector<std::filesystem::path> found_modules;

View File

@ -250,7 +250,7 @@ void Render(const vk::CommandBuffer& cmdbuf, const vk::ImageView& image_view,
}
bool MustKeepDrawing() {
return layers.size() > 1 || DebugState.IsShowingDebugMenuBar();
return layers.size() > 1 || change_layers.size() > 1 || DebugState.IsShowingDebugMenuBar();
}
} // namespace Core

View File

@ -559,7 +559,7 @@ void Translator::EmitFetch(const GcnInst& inst) {
std::filesystem::create_directories(dump_dir);
}
const auto filename = fmt::format("vs_{:#018x}.fetch.bin", info.pgm_hash);
const auto file = IOFile{dump_dir / filename, FileAccessMode::Write};
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create};
file.WriteRaw<u8>(fetch_data->code, fetch_data->size);
}

View File

@ -39,7 +39,7 @@ static void DumpSrtProgram(const Shader::Info& info, const u8* code, size_t code
std::filesystem::create_directories(dump_dir);
}
const auto filename = fmt::format("{}_{:#018x}.srtprogram.txt", info.stage, info.pgm_hash);
const auto file = IOFile{dump_dir / filename, FileAccessMode::Write, FileType::TextFile};
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create, FileType::TextFile};
u64 address = reinterpret_cast<u64>(code);
u64 code_end = address + codesize;

View File

@ -190,7 +190,7 @@ void CollectShaderInfoPass(IR::Program& program, const Profile& profile) {
});
info.buffers.push_back({
.used_types = IR::Type::U32,
.inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::FAULT_BUFFER_SIZE),
.inline_cbuf = AmdGpu::Buffer::Placeholder(std::numeric_limits<u32>::max()),
.buffer_type = BufferType::FaultBuffer,
.is_written = true,
});

View File

@ -28,7 +28,7 @@ void DumpProgram(const Program& program, const Info& info, const std::string& ty
}
const auto ir_filename =
fmt::format("{}_{:#018x}.{}irprogram.txt", info.stage, info.pgm_hash, type);
const auto ir_file = IOFile{dump_dir / ir_filename, FileAccessMode::Write, FileType::TextFile};
const auto ir_file = IOFile{dump_dir / ir_filename, FileAccessMode::Create, FileType::TextFile};
size_t index{0};
std::map<const IR::Inst*, size_t> inst_to_index;
@ -46,7 +46,7 @@ void DumpProgram(const Program& program, const Info& info, const std::string& ty
const auto asl_filename = fmt::format("{}_{:#018x}.{}asl.txt", info.stage, info.pgm_hash, type);
const auto asl_file =
IOFile{dump_dir / asl_filename, FileAccessMode::Write, FileType::TextFile};
IOFile{dump_dir / asl_filename, FileAccessMode::Create, FileType::TextFile};
for (const auto& node : program.syntax_list) {
std::string s = IR::DumpASLNode(node, block_to_index, inst_to_index) + '\n';

View File

@ -2,49 +2,42 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <mutex>
#include "common/alignment.h"
#include "common/debug.h"
#include "common/scope_exit.h"
#include "common/types.h"
#include "core/memory.h"
#include "video_core/amdgpu/liverpool.h"
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/buffer_cache/memory_tracker.h"
#include "video_core/host_shaders/fault_buffer_process_comp.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/texture_cache/texture_cache.h"
namespace VideoCore {
static constexpr size_t DataShareBufferSize = 64_KB;
static constexpr size_t StagingBufferSize = 512_MB;
static constexpr size_t DownloadBufferSize = 32_MB;
static constexpr size_t UboStreamBufferSize = 64_MB;
static constexpr size_t DownloadBufferSize = 128_MB;
static constexpr size_t DeviceBufferSize = 128_MB;
static constexpr size_t MaxPageFaults = 1024;
BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_,
PageManager& tracker)
: instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_},
memory{Core::Memory::Instance()}, texture_cache{texture_cache_},
fault_manager{instance, scheduler, *this, CACHING_PAGEBITS, CACHING_NUMPAGES},
staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize},
stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize},
download_buffer{instance, scheduler, MemoryUsage::Download, DownloadBufferSize},
device_buffer{instance, scheduler, MemoryUsage::DeviceLocal, DeviceBufferSize},
gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize},
bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal,
0, AllFlags, BDA_PAGETABLE_SIZE},
fault_buffer(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, FAULT_BUFFER_SIZE) {
0, AllFlags, BDA_PAGETABLE_SIZE} {
Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer");
Vulkan::SetObjectName(instance.GetDevice(), bda_pagetable_buffer.Handle(),
"BDA Page Table Buffer");
Vulkan::SetObjectName(instance.GetDevice(), fault_buffer.Handle(), "Fault Buffer");
memory_tracker = std::make_unique<MemoryTracker>(tracker);
@ -57,80 +50,6 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s
const vk::Buffer& null_buffer = slot_buffers[null_id].buffer;
Vulkan::SetObjectName(instance.GetDevice(), null_buffer, "Null Buffer");
// Prepare the fault buffer parsing pipeline
boost::container::static_vector<vk::DescriptorSetLayoutBinding, 2> bindings{
{
.binding = 0,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eCompute,
},
{
.binding = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eCompute,
},
};
const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = {
.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR,
.bindingCount = static_cast<u32>(bindings.size()),
.pBindings = bindings.data(),
};
auto [desc_layout_result, desc_layout] =
instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci);
ASSERT_MSG(desc_layout_result == vk::Result::eSuccess,
"Failed to create descriptor set layout: {}", vk::to_string(desc_layout_result));
fault_process_desc_layout = std::move(desc_layout);
const auto& module = Vulkan::Compile(HostShaders::FAULT_BUFFER_PROCESS_COMP,
vk::ShaderStageFlagBits::eCompute, instance.GetDevice());
Vulkan::SetObjectName(instance.GetDevice(), module, "Fault Buffer Parser");
const vk::SpecializationMapEntry specialization_map_entry = {
.constantID = 0,
.offset = 0,
.size = sizeof(u32),
};
const vk::SpecializationInfo specialization_info = {
.mapEntryCount = 1,
.pMapEntries = &specialization_map_entry,
.dataSize = sizeof(u32),
.pData = &CACHING_PAGEBITS,
};
const vk::PipelineShaderStageCreateInfo shader_ci = {
.stage = vk::ShaderStageFlagBits::eCompute,
.module = module,
.pName = "main",
.pSpecializationInfo = &specialization_info,
};
const vk::PipelineLayoutCreateInfo layout_info = {
.setLayoutCount = 1U,
.pSetLayouts = &(*fault_process_desc_layout),
};
auto [layout_result, layout] = instance.GetDevice().createPipelineLayoutUnique(layout_info);
ASSERT_MSG(layout_result == vk::Result::eSuccess, "Failed to create pipeline layout: {}",
vk::to_string(layout_result));
fault_process_pipeline_layout = std::move(layout);
const vk::ComputePipelineCreateInfo pipeline_info = {
.stage = shader_ci,
.layout = *fault_process_pipeline_layout,
};
auto [pipeline_result, pipeline] =
instance.GetDevice().createComputePipelineUnique({}, pipeline_info);
ASSERT_MSG(pipeline_result == vk::Result::eSuccess, "Failed to create compute pipeline: {}",
vk::to_string(pipeline_result));
fault_process_pipeline = std::move(pipeline);
Vulkan::SetObjectName(instance.GetDevice(), *fault_process_pipeline,
"Fault Buffer Parser Pipeline");
instance.GetDevice().destroyShaderModule(module);
// Set up garbage collection parameters
if (!instance.CanReportMemoryUsage()) {
trigger_gc_memory = DEFAULT_TRIGGER_GC_MEMORY;
@ -656,14 +575,10 @@ BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) {
wanted_size = static_cast<u32>(device_addr_end - device_addr);
const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size);
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id = [&] {
std::scoped_lock lk{slot_buffers_mutex};
return slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin,
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, size);
}();
const BufferId new_buffer_id =
slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin,
AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, size);
auto& new_buffer = slot_buffers[new_buffer_id];
const size_t size_bytes = new_buffer.SizeBytes();
const auto cmdbuf = scheduler.CommandBuffer();
for (const BufferId overlap_id : overlap.ids) {
JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
}
@ -672,126 +587,7 @@ BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) {
}
void BufferCache::ProcessFaultBuffer() {
// Run fault processing shader
const auto [mapped, offset] = download_buffer.Map(MaxPageFaults * sizeof(u64));
vk::BufferMemoryBarrier2 fault_buffer_barrier{
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eShaderWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader,
.dstAccessMask = vk::AccessFlagBits2::eShaderRead,
.buffer = fault_buffer.Handle(),
.offset = 0,
.size = FAULT_BUFFER_SIZE,
};
vk::BufferMemoryBarrier2 download_barrier{
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader,
.dstAccessMask = vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite,
.buffer = download_buffer.Handle(),
.offset = offset,
.size = MaxPageFaults * sizeof(u64),
};
std::array<vk::BufferMemoryBarrier2, 2> barriers{fault_buffer_barrier, download_barrier};
vk::DescriptorBufferInfo fault_buffer_info{
.buffer = fault_buffer.Handle(),
.offset = 0,
.range = FAULT_BUFFER_SIZE,
};
vk::DescriptorBufferInfo download_info{
.buffer = download_buffer.Handle(),
.offset = offset,
.range = MaxPageFaults * sizeof(u64),
};
boost::container::small_vector<vk::WriteDescriptorSet, 2> writes{
{
.dstSet = VK_NULL_HANDLE,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &fault_buffer_info,
},
{
.dstSet = VK_NULL_HANDLE,
.dstBinding = 1,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &download_info,
},
};
download_buffer.Commit();
scheduler.EndRendering();
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.fillBuffer(download_buffer.Handle(), offset, MaxPageFaults * sizeof(u64), 0);
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 2,
.pBufferMemoryBarriers = barriers.data(),
});
cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, *fault_process_pipeline);
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *fault_process_pipeline_layout, 0,
writes);
constexpr u32 num_threads = CACHING_NUMPAGES / 32; // 1 bit per page, 32 pages per workgroup
constexpr u32 num_workgroups = Common::DivCeil(num_threads, 64u);
cmdbuf.dispatch(num_workgroups, 1, 1);
// Reset fault buffer
const vk::BufferMemoryBarrier2 reset_pre_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader,
.srcAccessMask = vk::AccessFlagBits2::eShaderRead,
.dstStageMask = vk::PipelineStageFlagBits2::eTransfer,
.dstAccessMask = vk::AccessFlagBits2::eTransferWrite,
.buffer = fault_buffer.Handle(),
.offset = 0,
.size = FAULT_BUFFER_SIZE,
};
const vk::BufferMemoryBarrier2 reset_post_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite,
.buffer = fault_buffer.Handle(),
.offset = 0,
.size = FAULT_BUFFER_SIZE,
};
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &reset_pre_barrier,
});
cmdbuf.fillBuffer(fault_buffer.buffer, 0, FAULT_BUFFER_SIZE, 0);
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &reset_post_barrier,
});
// Defer creating buffers
scheduler.DeferOperation([this, mapped]() {
// Create the fault buffers batched
boost::icl::interval_set<VAddr> fault_ranges;
const u64* fault_ptr = std::bit_cast<const u64*>(mapped);
const u32 fault_count = static_cast<u32>(*(fault_ptr++));
for (u32 i = 0; i < fault_count; ++i) {
const VAddr fault = *(fault_ptr++);
const VAddr fault_end = fault + CACHING_PAGESIZE; // This can be adjusted
fault_ranges +=
boost::icl::interval_set<VAddr>::interval_type::right_open(fault, fault_end);
LOG_INFO(Render_Vulkan, "Accessed non-GPU cached memory at {:#x}", fault);
}
for (const auto& range : fault_ranges) {
const VAddr start = range.lower();
const VAddr end = range.upper();
const u64 page_start = start >> CACHING_PAGEBITS;
const u64 page_end = Common::DivCeil(end, CACHING_PAGESIZE);
// Buffer size is in 32 bits
ASSERT_MSG((range.upper() - range.lower()) <= std::numeric_limits<u32>::max(),
"Buffer size is too large");
CreateBuffer(start, static_cast<u32>(end - start));
}
});
fault_manager.ProcessFaultBuffer();
}
void BufferCache::Register(BufferId buffer_id) {
@ -889,7 +685,7 @@ bool BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size,
});
TouchBuffer(buffer);
}
if (is_texel_buffer) {
if (is_texel_buffer && !is_written) {
return SynchronizeBufferFromImage(buffer, device_addr, size);
}
return false;
@ -972,10 +768,7 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr,
}
void BufferCache::SynchronizeBuffersInRange(VAddr device_addr, u64 size) {
if (device_addr == 0) {
return;
}
VAddr device_addr_end = device_addr + size;
const VAddr device_addr_end = device_addr + size;
ForEachBufferInRange(device_addr, size, [&](BufferId buffer_id, Buffer& buffer) {
RENDERER_TRACE;
VAddr start = std::max(buffer.CpuAddr(), device_addr);
@ -985,21 +778,6 @@ void BufferCache::SynchronizeBuffersInRange(VAddr device_addr, u64 size) {
});
}
void BufferCache::MemoryBarrier() {
scheduler.EndRendering();
const auto cmdbuf = scheduler.CommandBuffer();
vk::MemoryBarrier2 barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eTransfer,
.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eMemoryRead,
};
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.memoryBarrierCount = 1,
.pMemoryBarriers = &barrier,
});
}
void BufferCache::InlineDataBuffer(Buffer& buffer, VAddr address, const void* value,
u32 num_bytes) {
scheduler.EndRendering();

View File

@ -3,12 +3,12 @@
#pragma once
#include <shared_mutex>
#include <boost/container/small_vector.hpp>
#include "common/lru_cache.h"
#include "common/slot_vector.h"
#include "common/types.h"
#include "video_core/buffer_cache/buffer.h"
#include "video_core/buffer_cache/fault_manager.h"
#include "video_core/buffer_cache/range_set.h"
#include "video_core/multi_level_page_table.h"
@ -40,9 +40,7 @@ public:
static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS;
static constexpr u64 DEVICE_PAGESIZE = 16_KB;
static constexpr u64 CACHING_NUMPAGES = u64{1} << (40 - CACHING_PAGEBITS);
static constexpr u64 BDA_PAGETABLE_SIZE = CACHING_NUMPAGES * sizeof(vk::DeviceAddress);
static constexpr u64 FAULT_BUFFER_SIZE = CACHING_NUMPAGES / 8; // Bit per page
// Default values for garbage collection
static constexpr s64 DEFAULT_TRIGGER_GC_MEMORY = 1_GB;
@ -68,12 +66,6 @@ public:
bool has_stream_leap = false;
};
using IntervalSet =
boost::icl::interval_set<VAddr, std::less,
ICL_INTERVAL_INSTANCE(ICL_INTERVAL_DEFAULT, VAddr, std::less),
RangeSetsAllocator>;
using IntervalType = typename IntervalSet::interval_type;
public:
explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
AmdGpu::Liverpool* liverpool, TextureCache& texture_cache,
@ -92,7 +84,7 @@ public:
/// Retrieves the fault buffer.
[[nodiscard]] Buffer* GetFaultBuffer() noexcept {
return &fault_buffer;
return fault_manager.GetFaultBuffer();
}
/// Retrieves the buffer with the specified id.
@ -160,9 +152,6 @@ public:
/// Synchronizes all buffers neede for DMA.
void SynchronizeDmaBuffers();
/// Record memory barrier. Used for buffers when accessed via BDA.
void MemoryBarrier();
/// Runs the garbage collector.
void RunGarbageCollector();
@ -217,6 +206,7 @@ private:
AmdGpu::Liverpool* liverpool;
Core::MemoryManager* memory;
TextureCache& texture_cache;
FaultManager fault_manager;
std::unique_ptr<MemoryTracker> memory_tracker;
StreamBuffer staging_buffer;
StreamBuffer stream_buffer;
@ -224,8 +214,6 @@ private:
StreamBuffer device_buffer;
Buffer gds_buffer;
Buffer bda_pagetable_buffer;
Buffer fault_buffer;
std::shared_mutex slot_buffers_mutex;
Common::SlotVector<Buffer> slot_buffers;
u64 total_used_memory = 0;
u64 trigger_gc_memory = 0;
@ -235,9 +223,6 @@ private:
RangeSet gpu_modified_ranges;
SplitRangeMap<BufferId> buffer_ranges;
PageTable page_table;
vk::UniqueDescriptorSetLayout fault_process_desc_layout;
vk::UniquePipeline fault_process_pipeline;
vk::UniquePipelineLayout fault_process_pipeline_layout;
};
} // namespace VideoCore

View File

@ -0,0 +1,177 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/div_ceil.h"
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/buffer_cache/fault_manager.h"
#include "video_core/renderer_vulkan/vk_instance.h"
#include "video_core/renderer_vulkan/vk_platform.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/host_shaders/fault_buffer_process_comp.h"
namespace VideoCore {
static constexpr size_t MaxPageFaults = 1024;
static constexpr size_t PageFaultAreaSize = MaxPageFaults * sizeof(u64);
FaultManager::FaultManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler_,
BufferCache& buffer_cache_, u32 caching_pagebits, u64 caching_num_pages_)
: scheduler{scheduler_}, buffer_cache{buffer_cache_},
caching_pagesize{1ULL << caching_pagebits}, caching_num_pages{caching_num_pages_},
fault_buffer_size{caching_num_pages_ / 8},
fault_buffer{instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, fault_buffer_size},
download_buffer{instance, scheduler, MemoryUsage::Download,
0, AllFlags, MaxPendingFaults * PageFaultAreaSize} {
const auto device = instance.GetDevice();
Vulkan::SetObjectName(device, fault_buffer.Handle(), "Fault Buffer");
const std::array<vk::DescriptorSetLayoutBinding, 2> bindings = {{
{
.binding = 0,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eCompute,
},
{
.binding = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eCompute,
},
}};
const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = {
.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR,
.bindingCount = 2,
.pBindings = bindings.data(),
};
fault_process_desc_layout =
Vulkan::Check(device.createDescriptorSetLayoutUnique(desc_layout_ci));
std::vector<std::string> defines{{fmt::format("CACHING_PAGEBITS={}", caching_pagebits),
fmt::format("MAX_PAGE_FAULTS={}", MaxPageFaults)}};
const auto module = Vulkan::Compile(HostShaders::FAULT_BUFFER_PROCESS_COMP,
vk::ShaderStageFlagBits::eCompute, device, defines);
Vulkan::SetObjectName(device, module, "Fault Buffer Parser");
const vk::PipelineShaderStageCreateInfo shader_ci = {
.stage = vk::ShaderStageFlagBits::eCompute,
.module = module,
.pName = "main",
};
const vk::PipelineLayoutCreateInfo layout_info = {
.setLayoutCount = 1U,
.pSetLayouts = &(*fault_process_desc_layout),
};
fault_process_pipeline_layout = Vulkan::Check(device.createPipelineLayoutUnique(layout_info));
const vk::ComputePipelineCreateInfo pipeline_info = {
.stage = shader_ci,
.layout = *fault_process_pipeline_layout,
};
fault_process_pipeline = Vulkan::Check(device.createComputePipelineUnique({}, pipeline_info));
Vulkan::SetObjectName(device, *fault_process_pipeline, "Fault Buffer Parser Pipeline");
device.destroyShaderModule(module);
}
void FaultManager::ProcessFaultBuffer() {
if (u64 wait_tick = fault_areas[current_area]) {
scheduler.Wait(wait_tick);
scheduler.PopPendingOperations();
}
const u32 offset = current_area * PageFaultAreaSize;
u8* mapped = download_buffer.mapped_data.data() + offset;
std::memset(mapped, 0, PageFaultAreaSize);
const vk::BufferMemoryBarrier2 pre_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eShaderWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader,
.dstAccessMask = vk::AccessFlagBits2::eShaderRead,
.buffer = fault_buffer.Handle(),
.offset = 0,
.size = fault_buffer_size,
};
const vk::BufferMemoryBarrier2 post_barrier = {
.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader,
.srcAccessMask = vk::AccessFlagBits2::eShaderWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eShaderWrite,
.buffer = fault_buffer.Handle(),
.offset = 0,
.size = fault_buffer_size,
};
const vk::DescriptorBufferInfo fault_buffer_info = {
.buffer = fault_buffer.Handle(),
.offset = 0,
.range = fault_buffer_size,
};
const vk::DescriptorBufferInfo download_info = {
.buffer = download_buffer.Handle(),
.offset = offset,
.range = PageFaultAreaSize,
};
const std::array<vk::WriteDescriptorSet, 2> writes = {{
{
.dstSet = VK_NULL_HANDLE,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &fault_buffer_info,
},
{
.dstSet = VK_NULL_HANDLE,
.dstBinding = 1,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eStorageBuffer,
.pBufferInfo = &download_info,
},
}};
scheduler.EndRendering();
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &pre_barrier,
});
cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, *fault_process_pipeline);
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *fault_process_pipeline_layout, 0,
writes);
// 1 bit per page, 32 pages per workgroup
const u32 num_threads = caching_num_pages / 32;
const u32 num_workgroups = Common::DivCeil(num_threads, 64u);
cmdbuf.dispatch(num_workgroups, 1, 1);
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &post_barrier,
});
scheduler.DeferOperation([this, mapped, area = current_area] {
fault_ranges.Clear();
const u64* fault_buf = std::bit_cast<const u64*>(mapped);
const u32 fault_count = fault_buf[0];
for (u32 i = 1; i <= fault_count; ++i) {
fault_ranges.Add(fault_buf[i], caching_pagesize);
LOG_INFO(Render_Vulkan, "Accessed non-GPU cached memory at {:#x}", fault_buf[i]);
}
fault_ranges.ForEach([&](VAddr start, VAddr end) {
ASSERT_MSG((end - start) <= std::numeric_limits<u32>::max(),
"Buffer size is too large");
buffer_cache.FindBuffer(start, static_cast<u32>(end - start));
});
fault_areas[area] = 0;
});
fault_areas[current_area++] = scheduler.CurrentTick();
current_area %= MaxPendingFaults;
}
} // namespace VideoCore

View File

@ -0,0 +1,42 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "video_core/buffer_cache/buffer.h"
#include "video_core/buffer_cache/range_set.h"
namespace VideoCore {
class BufferCache;
class FaultManager {
static constexpr size_t MaxPendingFaults = 8;
public:
explicit FaultManager(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
BufferCache& buffer_cache, u32 caching_pagebits, u64 caching_num_pages);
[[nodiscard]] Buffer* GetFaultBuffer() noexcept {
return &fault_buffer;
}
void ProcessFaultBuffer();
private:
Vulkan::Scheduler& scheduler;
BufferCache& buffer_cache;
RangeSet fault_ranges;
u64 caching_pagesize;
u64 caching_num_pages;
u64 fault_buffer_size;
Buffer fault_buffer;
Buffer download_buffer;
std::array<u64, MaxPendingFaults> fault_areas{};
u32 current_area{};
vk::UniqueDescriptorSetLayout fault_process_desc_layout;
vk::UniquePipeline fault_process_pipeline;
vk::UniquePipelineLayout fault_process_pipeline_layout;
};
} // namespace VideoCore

View File

@ -13,30 +13,23 @@ layout(std430, binding = 0) buffer input_buf {
layout(std430, binding = 1) buffer output_buf {
uint64_t download_buffer[];
};
// Overlap for 32 bit atomics
layout(std430, binding = 1) buffer output_buf32 {
uint download_buffer32[];
};
layout(constant_id = 0) const uint CACHING_PAGEBITS = 0;
void main() {
uint id = gl_GlobalInvocationID.x;
const uint id = gl_GlobalInvocationID.x;
uint word = fault_buffer[id];
if (word == 0u) {
return;
}
// 1 page per bit
uint base_bit = id * 32u;
fault_buffer[id] = 0u;
const uint base_bit = id * 32u;
while (word != 0u) {
uint bit = findLSB(word);
word &= word - 1;
uint page = base_bit + bit;
uint store_index = atomicAdd(download_buffer32[0], 1u) + 1u;
// It is very unlikely, but should we check for overflow?
if (store_index < 1024u) { // only support 1024 page faults
download_buffer[store_index] = uint64_t(page) << CACHING_PAGEBITS;
const uint store_index = atomicAdd(download_buffer32[0], 1u) + 1u;
if (store_index >= MAX_PAGE_FAULTS) {
return;
}
const uint bit = findLSB(word);
word &= word - 1;
const uint page = base_bit + bit;
download_buffer[store_index] = uint64_t(page) << CACHING_PAGEBITS;
}
}

View File

@ -355,13 +355,12 @@ bool PipelineCache::RefreshGraphicsKey() {
}
// Fill color target information
key.color_buffers[cb] = Shader::PsColorBuffer{
.data_format = col_buf.GetDataFmt(),
.num_format = col_buf.GetNumberFmt(),
.num_conversion = col_buf.GetNumberConversion(),
.export_format = regs.color_export_format.GetFormat(cb),
.swizzle = col_buf.Swizzle(),
};
auto& color_buffer = key.color_buffers[cb];
color_buffer.data_format = col_buf.GetDataFmt();
color_buffer.num_format = col_buf.GetNumberFmt();
color_buffer.num_conversion = col_buf.GetNumberConversion();
color_buffer.export_format = regs.color_export_format.GetFormat(cb);
color_buffer.swizzle = col_buf.Swizzle();
}
// Compile and bind shader stages
@ -379,7 +378,7 @@ bool PipelineCache::RefreshGraphicsKey() {
continue;
}
if ((key.mrt_mask & (1u << cb)) == 0) {
key.color_buffers[cb] = {};
std::memset(&key.color_buffers[cb], 0, sizeof(Shader::PsColorBuffer));
continue;
}
@ -632,7 +631,7 @@ void PipelineCache::DumpShader(std::span<const u32> code, u64 hash, Shader::Stag
std::filesystem::create_directories(dump_dir);
}
const auto filename = fmt::format("{}.{}", GetShaderName(stage, hash, perm_idx), ext);
const auto file = IOFile{dump_dir / filename, FileAccessMode::Write};
const auto file = IOFile{dump_dir / filename, FileAccessMode::Create};
file.WriteSpan(code);
}

View File

@ -380,7 +380,8 @@ void Rasterizer::OnSubmit() {
}
bool Rasterizer::BindResources(const Pipeline* pipeline) {
if (IsComputeImageCopy(pipeline) || IsComputeMetaClear(pipeline)) {
if (IsComputeImageCopy(pipeline) || IsComputeMetaClear(pipeline) ||
IsComputeImageClear(pipeline)) {
return false;
}
@ -406,18 +407,13 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) {
if (uses_dma) {
// We only use fault buffer for DMA right now.
{
Common::RecursiveSharedLock lock{mapped_ranges_mutex};
for (auto& range : mapped_ranges) {
buffer_cache.SynchronizeBuffersInRange(range.lower(),
range.upper() - range.lower());
}
Common::RecursiveSharedLock lock{mapped_ranges_mutex};
for (auto& range : mapped_ranges) {
buffer_cache.SynchronizeBuffersInRange(range.lower(), range.upper() - range.lower());
}
buffer_cache.MemoryBarrier();
fault_process_pending = true;
}
fault_process_pending |= uses_dma;
return true;
}
@ -520,6 +516,66 @@ bool Rasterizer::IsComputeImageCopy(const Pipeline* pipeline) {
return true;
}
bool Rasterizer::IsComputeImageClear(const Pipeline* pipeline) {
if (!pipeline->IsCompute()) {
return false;
}
// Ensure shader only has 2 bound buffers
const auto& cs_pgm = liverpool->GetCsRegs();
const auto& info = pipeline->GetStage(Shader::LogicalStage::Compute);
if (cs_pgm.num_thread_x.full != 64 || info.buffers.size() != 2 || !info.images.empty()) {
return false;
}
// From those 2 buffers, first must hold the clear vector and second the image being cleared
const auto& desc0 = info.buffers[0];
const auto& desc1 = info.buffers[1];
if (desc0.is_formatted || !desc1.is_formatted || desc0.is_written || !desc1.is_written) {
return false;
}
// First buffer must have size of vec4 and second the size of a single layer
const AmdGpu::Buffer buf0 = desc0.GetSharp(info);
const AmdGpu::Buffer buf1 = desc1.GetSharp(info);
const u32 buf1_bpp = AmdGpu::NumBitsPerBlock(buf1.GetDataFmt());
if (buf0.GetSize() != 16 || (cs_pgm.dim_x * 128ULL * (buf1_bpp / 8)) != buf1.GetSize()) {
return false;
}
// Find image the buffer alias
const auto image1_id =
texture_cache.FindImageFromRange(buf1.base_address, buf1.GetSize(), false);
if (!image1_id) {
return false;
}
// Image clear must be valid
VideoCore::Image& image1 = texture_cache.GetImage(image1_id);
if (image1.info.guest_size != buf1.GetSize() || image1.info.num_bits != buf1_bpp ||
image1.info.props.is_depth) {
return false;
}
// Perform image clear
const float* values = reinterpret_cast<float*>(buf0.base_address);
const vk::ClearValue clear = {
.color = {.float32 = std::array<float, 4>{values[0], values[1], values[2], values[3]}},
};
const VideoCore::SubresourceRange range = {
.base =
{
.level = 0,
.layer = 0,
},
.extent = image1.info.resources,
};
image1.Clear(clear, range);
image1.flags |= VideoCore::ImageFlagBits::GpuModified;
image1.flags &= ~VideoCore::ImageFlagBits::Dirty;
return true;
}
void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Bindings& binding,
Shader::PushData& push_data) {
buffer_bindings.clear();

View File

@ -112,6 +112,7 @@ private:
bool IsComputeMetaClear(const Pipeline* pipeline);
bool IsComputeImageCopy(const Pipeline* pipeline);
bool IsComputeImageClear(const Pipeline* pipeline);
private:
friend class VideoCore::BufferCache;

View File

@ -84,15 +84,6 @@ void Scheduler::Wait(u64 tick) {
Flush(info);
}
master_semaphore.Wait(tick);
// CAUTION: This can introduce unexpected variation in the wait time.
// We don't currently sync the GPU, and some games are very sensitive to this.
// If this becomes a problem, it can be commented out.
// Idealy we would implement proper gpu sync.
while (!pending_ops.empty() && pending_ops.front().gpu_tick <= tick) {
pending_ops.front().callback();
pending_ops.pop();
}
}
void Scheduler::PopPendingOperations() {
@ -109,9 +100,7 @@ void Scheduler::AllocateWorkerCommandBuffers() {
};
current_cmdbuf = command_pool.Commit();
auto begin_result = current_cmdbuf.begin(begin_info);
ASSERT_MSG(begin_result == vk::Result::eSuccess, "Failed to begin command buffer: {}",
vk::to_string(begin_result));
Check(current_cmdbuf.begin(begin_info));
// Invalidate dynamic state so it gets applied to the new command buffer.
dynamic_state.Invalidate();
@ -139,9 +128,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) {
#endif
EndRendering();
auto end_result = current_cmdbuf.end();
ASSERT_MSG(end_result == vk::Result::eSuccess, "Failed to end command buffer: {}",
vk::to_string(end_result));
Check(current_cmdbuf.end());
const vk::Semaphore timeline = master_semaphore.Handle();
info.AddSignal(timeline, signal_value);