Merge remote-tracking branch 'upstream/main' into feat/agc-predication

# Conflicts:
#	docs/dev/TechnicalDebt.md
This commit is contained in:
Chad 2026-10-05 18:45:17 -05:00
commit 73a751ac1f
127 changed files with 4603 additions and 465 deletions

View File

@ -83,6 +83,19 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_packet_info_tests EXCLUDE_FROM_ALL tests/PacketInfo.cpp)
target_include_directories(agc_packet_info_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_packet_info_tests PRIVATE libSceAgc libc)
configure_windows_unwind(agc_packet_info_tests)
if(BUILD_TESTING)
set_target_properties(agc_packet_info_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_packet_info_tests COMMAND agc_packet_info_tests)
if(WIN32)
set_tests_properties(agc_packet_info_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_register_command_tests EXCLUDE_FROM_ALL
tests/RegisterCommands.cpp
Command/src/Packet.cpp
@ -102,6 +115,19 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_payload_range_tests EXCLUDE_FROM_ALL tests/PayloadRange.cpp)
target_include_directories(agc_payload_range_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_payload_range_tests PRIVATE libSceAgc libc)
configure_windows_unwind(agc_payload_range_tests)
if(BUILD_TESTING)
set_target_properties(agc_payload_range_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_payload_range_tests COMMAND agc_payload_range_tests)
if(WIN32)
set_tests_properties(agc_payload_range_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_branch_patch_tests EXCLUDE_FROM_ALL
tests/BranchPatch.cpp
Command/src/Packet.cpp
@ -220,3 +246,23 @@ if(BUILD_TESTING)
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_cond_exec_patch_tests EXCLUDE_FROM_ALL
tests/CondExecPatch.cpp
Command/src/Packet.cpp
Command/src/Memory.cpp
Command/src/Control.cpp
DcbFlow/src/Sync.cpp
Patch/src/CondExec.cpp
)
target_include_directories(agc_cond_exec_patch_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(agc_cond_exec_patch_tests PRIVATE libc)
configure_windows_unwind(agc_cond_exec_patch_tests)
if(BUILD_TESTING)
set_target_properties(agc_cond_exec_patch_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_test(NAME agc_cond_exec_patch_tests COMMAND agc_cond_exec_patch_tests)
if(WIN32)
set_tests_properties(agc_cond_exec_patch_tests PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()

View File

@ -1,4 +1,11 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PACKETINFO_HPP
#define CORE_LIBS_PRX_LIBSCEAGC_MISC_INCLUDE_PACKETINFO_HPP
#include <cstdint>
struct SceAgcMemoryRange {
void* base;
uint64_t size;
};
#endif

View File

@ -9,9 +9,12 @@
extern "C" {
uint32_t APS5_VABI sceAgcGetPacketSize(uint32_t* packet) {
(void)packet;
NotImplemented_nid_no_patch(__func__);
return 0;
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(packet), alignof(uint32_t), __func__);
Agc::Command::Require((packet[0] & 0xc0000000u) == 0xc0000000u, __func__, "source pointer is not a PM4 packet");
if ((packet[0] & 0x3fffff00u) == 0x3fff1000u) {
return 1;
}
return ((packet[0] >> 16u) & 0x3fffu) + 2u;
}
APS5_EXPORT("V++UgBtQhn0", sceAgcGetDataPacketPayloadAddressUnk);
@ -26,6 +29,23 @@ int APS5_VABI sceAgcGetDataPacketPayloadAddressUnk(uint32_t** addr, uint32_t* cm
return 0;
}
int APS5_VABI sceAgcGetDataPacketPayloadRange(SceAgcMemoryRange* range, uint32_t* cmd, int type) {
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(range), alignof(SceAgcMemoryRange), __func__);
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(cmd), alignof(uint32_t), __func__);
const uint64_t count_bytes = (cmd[0] >> 14u) & 0xfffcu;
if (type != 0) {
range->base = cmd + 2;
range->size = count_bytes;
} else if ((~cmd[0] & 0x3fff0000u) == 0) {
range->base = nullptr;
range->size = 0;
} else {
range->base = cmd + 1;
range->size = count_bytes + sizeof(uint32_t);
}
return 0;
}
int APS5_VABI sceAgcDcbGetSystemSoftwareVersion(uint32_t* destination, const uint32_t* packet) {
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(destination), alignof(uint32_t), __func__);
Agc::Command::CheckAddress(reinterpret_cast<std::uintptr_t>(packet), alignof(uint32_t), __func__);

View File

@ -10,17 +10,23 @@
extern "C" {
int APS5_VABI sceAgcCondExecPatchSetCommandAddress(uint32_t* cmd, const volatile uint32_t* command) {
(void)cmd;
(void)command;
NotImplemented_nid_no_patch(__func__);
return 0;
Agc::Command::ValidatePacket(cmd, 0x22u, 5, __func__);
const auto address = reinterpret_cast<std::uintptr_t>(command);
Agc::Command::CheckAddress(address, 4, __func__);
cmd[1] = (cmd[1] & 3u) | (static_cast<std::uint32_t>(address) & ~3u);
cmd[2] = static_cast<std::uint32_t>(address >> 32u);
return 0;
}
int APS5_VABI sceAgcCondExecPatchSetEnd(uint32_t* cmd, const volatile uint32_t* buffer) {
(void)cmd;
(void)buffer;
NotImplemented_nid_no_patch(__func__);
return 0;
Agc::Command::ValidatePacket(cmd, 0x22u, 5, __func__);
const auto end = reinterpret_cast<std::uintptr_t>(buffer);
const auto packetEnd = reinterpret_cast<std::uintptr_t>(cmd + 5);
Agc::Command::CheckAddress(end, 4, __func__);
const auto numDwords = (end - packetEnd) / sizeof(std::uint32_t);
Agc::Command::Require(numDwords <= 0x3fffu, __func__, "conditional execution range is too large");
cmd[4] = (cmd[4] & ~0x3fffu) | static_cast<std::uint32_t>(numDwords);
return 0;
}
int APS5_VABI sceAgcAsyncCondExecPatchSetCommandAddress(std::uint32_t* cmd, const volatile std::uint32_t* command) {

View File

@ -55,11 +55,6 @@ int APS5_VABI sceAgcDebugRaiseException() {
return 0;
}
int APS5_VABI sceAgcGetDataPacketPayloadRange() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAgcGetDefaultCxStateFlat() {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -137,4 +132,10 @@ int APS5_VABI sceAgcAsyncWriteDataPatchSetCachePolicy() {
return 0;
}
APS5_EXPORT("vieBRwlh1Lw", sceAgcUnknown_vieBRwlh1Lw);
int APS5_VABI sceAgcUnknown_vieBRwlh1Lw(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -0,0 +1,107 @@
#include "prx/libSceAgc/Command/include/Packet.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
extern "C" {
std::uint32_t* APS5_VABI sceAgcDcbCondExec(CommandBuffer*, const volatile std::uint32_t*, std::uint32_t);
int APS5_VABI sceAgcCondExecPatchSetCommandAddress(std::uint32_t*, const volatile std::uint32_t*);
int APS5_VABI sceAgcCondExecPatchSetEnd(std::uint32_t*, const volatile std::uint32_t*);
}
namespace {
constexpr std::uint32_t Sentinel = 0xabcdef01u;
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
template <typename TAction>
void expectFailure(TAction action) {
try {
action();
} catch (const std::runtime_error&) {
return;
}
throw std::runtime_error("expected invalid input to fail");
}
const volatile std::uint32_t* at(std::uintptr_t address) {
return reinterpret_cast<const volatile std::uint32_t*>(address);
}
struct Storage {
std::array<std::uint32_t, 16> words{};
CommandBuffer buffer{words.data(), words.data() + words.size(), words.data(), words.data() + words.size(), nullptr, nullptr, 0};
std::uint32_t condition = 1;
std::uint32_t* packet = nullptr;
Storage() {
words.fill(Sentinel);
packet = sceAgcDcbCondExec(&buffer, &condition, 3);
check(packet == words.data() && packet[0] == 0xc0032200u && packet[4] == 3, "unexpected COND_EXEC packet");
}
};
std::uintptr_t endAfter(const std::uint32_t* packet, std::uintptr_t numDwords) {
return reinterpret_cast<std::uintptr_t>(packet + 5) + numDwords * sizeof(std::uint32_t);
}
template <typename TAction>
void expectUnchanged(TAction action) {
Storage storage;
const auto before = storage.words;
expectFailure([&] { action(storage.packet); });
check(storage.words == before, "failed patch modified the packet");
}
void testSetEnd() {
for (const std::uintptr_t numDwords : {0u, 1u, 7u, 0x3fffu}) {
Storage storage;
const auto before = storage.words;
check(sceAgcCondExecPatchSetEnd(storage.packet, at(endAfter(storage.packet, numDwords))) == 0, "SetEnd failed");
check(storage.packet[4] == numDwords, "SetEnd wrote the wrong dword count");
check(std::equal(before.begin(), before.begin() + 4, storage.words.begin()), "SetEnd modified other packet words");
check(std::equal(before.begin() + 5, before.end(), storage.words.begin() + 5), "SetEnd modified following words");
}
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, at(endAfter(packet, 0x4000u))); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, packet + 4); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, nullptr); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet, at(endAfter(packet, 2) + 2)); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetEnd(packet + 1, at(endAfter(packet, 2))); });
}
void testSetCommandAddress() {
for (const std::uintptr_t address : {std::uintptr_t{0x0000123456789abcu}, std::uintptr_t{4u}, std::uintptr_t{0x0000fffffffffffcu}}) {
Storage storage;
const auto before = storage.words;
check(sceAgcCondExecPatchSetCommandAddress(storage.packet, at(address)) == 0, "SetCommandAddress failed");
check(storage.packet[1] == static_cast<std::uint32_t>(address) && storage.packet[2] == static_cast<std::uint32_t>(address >> 32u), "SetCommandAddress wrote the wrong address");
check(storage.packet[0] == before[0] && storage.packet[3] == before[3] && storage.packet[4] == before[4], "SetCommandAddress modified other packet words");
check(std::equal(before.begin() + 5, before.end(), storage.words.begin() + 5), "SetCommandAddress modified following words");
}
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetCommandAddress(packet, nullptr); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetCommandAddress(packet, at(0x1002u)); });
expectUnchanged([](std::uint32_t* packet) { sceAgcCondExecPatchSetCommandAddress(packet + 1, at(0x1000u)); });
}
}
int main() {
try {
testSetEnd();
testSetCommandAddress();
std::puts("AGC COND_EXEC patch tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
return 1;
}
}

View File

@ -0,0 +1,66 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <utility>
extern "C" std::uint32_t APS5_VABI sceAgcGetPacketSize(std::uint32_t* packet);
namespace {
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
template <typename TAction>
void expectFailure(TAction action) {
try {
action();
} catch (const std::runtime_error& error) {
check(error.what()[0] != '\0', "empty exception message");
return;
}
throw std::runtime_error("expected an exception");
}
void testPacketSize() {
const std::array<std::pair<std::uint32_t, std::uint32_t>, 7> sizes{{
{0xc0047600u, 6}, {0xc0001000u, 2}, {0xfffe1000u, 0x4000}, {0xffff7600u, 0x4001},
{0xffff1000u, 1}, {0xffff1001u, 1}, {0xffff10fcu, 1}}};
for (const auto& [header, size] : sizes) {
std::array<std::uint32_t, 2> words{header, 0xffff1000u};
check(sceAgcGetPacketSize(words.data()) == size, "packet size mismatch");
check(words[0] == header && words[1] == 0xffff1000u, "packet size query modified the packet");
}
}
void testRejections() {
for (const auto header : {0x80000000u, 0x3fff1000u, 0x40001000u, 0x00047600u}) {
std::uint32_t word = header;
expectFailure([&] { sceAgcGetPacketSize(&word); });
}
expectFailure([] { sceAgcGetPacketSize(nullptr); });
std::array<std::uint32_t, 2> words{0x00100000u, 0x000000c0u};
auto* misaligned = reinterpret_cast<std::uint32_t*>(reinterpret_cast<unsigned char*>(words.data()) + 1);
expectFailure([&] { sceAgcGetPacketSize(misaligned); });
}
}
int main() {
try {
testPacketSize();
testRejections();
LibcRunShutdown_nid_postfix();
std::puts("AGC packet info tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
try { LibcRunShutdown_nid_postfix(); }
catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); }
return 1;
}
}

View File

@ -0,0 +1,80 @@
#include "prx/libSceAgc/Misc/include/PacketInfo.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <tuple>
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadRange(SceAgcMemoryRange* range, std::uint32_t* cmd, int type);
extern "C" int APS5_VABI sceAgcGetDataPacketPayloadAddressUnk(std::uint32_t** addr, std::uint32_t* cmd, int type);
namespace {
void check(bool condition, const char* message) {
if (!condition) throw std::runtime_error(message);
}
template <typename TAction>
void expectFailure(TAction action) {
try {
action();
} catch (const std::runtime_error& error) {
check(error.what()[0] != '\0', "empty exception message");
return;
}
throw std::runtime_error("expected an exception");
}
void testRanges() {
const std::array<std::tuple<std::uint32_t, int, std::ptrdiff_t, std::uint64_t>, 10> rows{{
{0xc0047600u, 1, 2, 16}, {0xc0001000u, 1, 2, 0}, {0xffff7600u, 1, 2, 0xfffc}, {0xc0047600u, -1, 2, 16},
{0xc0047600u, 7, 2, 16}, {0xc0021000u, 0, 1, 12}, {0xc0001000u, 0, 1, 4}, {0xfffe1000u, 0, 1, 0xfffc},
{0xffff1000u, 0, -1, 0}, {0xffff7600u, 0, -1, 0}}};
for (const auto& [header, type, offset, size] : rows) {
std::array<std::uint32_t, 2> words{header, 0x12345678u};
SceAgcMemoryRange range{reinterpret_cast<void*>(std::uintptr_t{0x1000}), 0xdeadu};
check(sceAgcGetDataPacketPayloadRange(&range, words.data(), type) == 0, "payload range failed");
void* expected = offset < 0 ? nullptr : static_cast<void*>(words.data() + offset);
check(range.base == expected, "payload range base mismatch");
check(range.size == size, "payload range size mismatch");
check(words[0] == header && words[1] == 0x12345678u, "payload range query modified the packet");
std::uint32_t* address = nullptr;
check(sceAgcGetDataPacketPayloadAddressUnk(&address, words.data(), type) == 0 && address == range.base,
"payload range base differs from the payload address");
}
}
void testRejections() {
std::array<std::uint32_t, 2> words{0xc0047600u, 0u};
SceAgcMemoryRange range{};
expectFailure([&] { sceAgcGetDataPacketPayloadRange(nullptr, words.data(), 1); });
expectFailure([&] { sceAgcGetDataPacketPayloadRange(&range, nullptr, 1); });
auto* misaligned = reinterpret_cast<std::uint32_t*>(reinterpret_cast<unsigned char*>(words.data()) + 1);
expectFailure([&] { sceAgcGetDataPacketPayloadRange(&range, misaligned, 0); });
std::array<unsigned char, sizeof(SceAgcMemoryRange) + 8> storage{};
auto* misalignedRange = reinterpret_cast<SceAgcMemoryRange*>(storage.data() + 4);
if (reinterpret_cast<std::uintptr_t>(misalignedRange) % alignof(SceAgcMemoryRange) == 0) {
misalignedRange = reinterpret_cast<SceAgcMemoryRange*>(storage.data() + 2);
}
expectFailure([&] { sceAgcGetDataPacketPayloadRange(misalignedRange, words.data(), 1); });
}
}
int main() {
try {
testRanges();
testRejections();
LibcRunShutdown_nid_postfix();
std::puts("AGC payload range tests passed");
return 0;
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
try { LibcRunShutdown_nid_postfix(); }
catch (const std::exception& shutdown) { std::fprintf(stderr, "shutdown: %s\n", shutdown.what()); }
return 1;
}
}

View File

@ -303,6 +303,20 @@ if(BUILD_TESTING)
endif()
endif()
add_test_executable(agc_display_buffer_dcc_tests EXCLUDE_FROM_ALL tests/DisplayBufferDcc.cpp)
target_include_directories(agc_display_buffer_dcc_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/core/shader/recompiler ${CMAKE_SOURCE_DIR}/3rdparty/Vulkan-Headers/include)
target_link_libraries(agc_display_buffer_dcc_tests PRIVATE libSceAgcDriver libc)
set_target_properties(agc_display_buffer_dcc_tests PROPERTIES CXX_EXTENSIONS OFF)
configure_windows_unwind(agc_display_buffer_dcc_tests)
if(BUILD_TESTING)
set_target_properties(agc_display_buffer_dcc_tests PROPERTIES EXCLUDE_FROM_ALL OFF)
add_test(NAME agc_display_buffer_dcc COMMAND agc_display_buffer_dcc_tests)
if(WIN32)
set_tests_properties(agc_display_buffer_dcc PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
add_test_executable(agc_validate_program_tests EXCLUDE_FROM_ALL tests/ValidateProgram.cpp)
target_link_libraries(agc_validate_program_tests PRIVATE shader_recompiler_agc_driver libc)
set_target_properties(agc_validate_program_tests PROPERTIES CXX_EXTENSIONS OFF)

View File

@ -1,6 +1,7 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DISPLAYBUFFER_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_EXECUTION_INCLUDE_DISPLAYBUFFER_HPP
#include <array>
#include <cstddef>
#include <cstdint>
#include <span>
@ -8,6 +9,10 @@
namespace AgcDriver {
namespace Graphics {
enum class DccKeys;
}
struct DisplayBuffer {
std::uint64_t address;
std::uint64_t pixelFormat;
@ -15,11 +20,14 @@ struct DisplayBuffer {
std::uint32_t height;
std::uint32_t tilingMode = 0;
std::uint32_t pitchInPixel = 0;
std::uint64_t dccAddress = 0;
std::uint64_t dccClearColor = 0;
};
std::size_t DisplayBufferSize(const DisplayBuffer& buffer);
std::vector<std::byte> DecodeDisplayBuffer(const DisplayBuffer& buffer, std::span<const std::byte> source);
std::vector<std::byte> ReadDisplayBuffer(const DisplayBuffer& buffer);
std::array<std::byte, 4> DisplayBufferClearPixel(const DisplayBuffer& buffer, Graphics::DccKeys keys);
}

View File

@ -88,6 +88,8 @@ bool AccessesMemory(std::uint32_t header);
// synchronization): such a packet needs a pipeline barrier, not a device drain.
bool UsesGpuCacheBarrier(std::span<const std::uint32_t> packet);
bool IsTagMarker(std::span<const std::uint32_t> packet);
inline bool Predicated(std::uint32_t header) { return !FillerPacket(header) && (header & 1u) != 0; }
bool PredicationPasses(const QueueState& queue);
bool WaitSatisfied(std::span<const std::uint32_t> packet);
// WaitSatisfied for a polling loop: the caller has validated the address once, so the value is read
// directly instead of through the checked guest memory path.

View File

@ -17,6 +17,7 @@ public:
void EnsureSourceImage(std::uint32_t width, std::uint32_t height);
void RecordUpload(VkCommandBuffer commands, VkBuffer uploadBuffer);
void RecordClear(VkCommandBuffer commands, const VkClearColorValue& color);
void RecordImage(VkCommandBuffer commands, VkImage image);
// Fills the source image from another image of the same extent (any blittable format, layout
// unchanged), leaving the source image ready to blit or read back.

View File

@ -167,6 +167,12 @@ inline Registers InitialContextRegisters() {
return result;
}
struct Predication {
std::uint64_t address = 0;
std::uint32_t operation = 0;
bool executeWhenSet = false;
};
struct QueueState {
Registers shader;
Registers context = InitialContextRegisters();
@ -180,6 +186,7 @@ struct QueueState {
std::uint32_t indexType = 0;
std::uint32_t instanceCount = 1;
std::vector<std::string> markers;
Predication predication;
void ClearContext() {
context = InitialContextRegisters();

View File

@ -307,7 +307,7 @@ private:
// The tail of a dispatch's device call from the open batch's command buffer to the completion
// registration: keeps, the template data refresh, barriers, bind, push, dispatch, marks.
void recordDispatch(RecordedDispatch& record);
bool present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display = nullptr, const std::shared_ptr<Graphics::StorageTexture>& resident = nullptr, VkFilter residentFilter = VK_FILTER_LINEAR, bool dumpFrame = false, bool residentConvert = false);
bool present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display = nullptr, const std::shared_ptr<Graphics::StorageTexture>& resident = nullptr, VkFilter residentFilter = VK_FILTER_LINEAR, bool dumpFrame = false, bool residentConvert = false, const VkClearColorValue* uniform = nullptr);
struct State;
std::unique_ptr<State> state;
std::uint64_t serial;

View File

@ -1,6 +1,9 @@
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
#include "prx/libSceAgcDriver/Execution/include/DisplayFormat.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libSceAgcDriver/Execution/include/PerformanceTimer.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include <array>
#include <chrono>
#include <cstdio>
#include <cstdlib>
@ -28,6 +31,25 @@ std::uint32_t tileOffset(std::uint32_t x, std::uint32_t y) {
return ((y << 4u) & 0x0070u) ^ ((y << 5u) & 0x0f00u) ^ ((y << 9u) & 0x1000u) ^ ((y << 8u) & 0x4000u) ^ ((x << 2u) & 0x000cu) ^ ((x << 5u) & 0x0380u) ^ ((x << 4u) & 0x0400u) ^ ((x << 6u) & 0x0800u) ^ ((x << 9u) & 0xa000u);
}
void decodeTexel(const std::byte* texel, std::byte* pixel, bool rgba, bool tenBit) {
if (tenBit) {
std::uint32_t word = 0;
std::memcpy(&word, texel, 4);
const auto red = static_cast<std::byte>((word >> 2u) & 0xffu);
const auto green = static_cast<std::byte>((word >> 12u) & 0xffu);
const auto blue = static_cast<std::byte>((word >> 22u) & 0xffu);
pixel[0] = rgba ? blue : red;
pixel[1] = green;
pixel[2] = rgba ? red : blue;
pixel[3] = std::byte{0xff};
return;
}
pixel[0] = texel[rgba ? 2 : 0];
pixel[1] = texel[1];
pixel[2] = texel[rgba ? 0 : 2];
pixel[3] = texel[3];
}
}
std::size_t DisplayBufferSize(const DisplayBuffer& buffer) {
@ -40,6 +62,8 @@ std::size_t DisplayBufferSize(const DisplayBuffer& buffer) {
require(buffer.address != 0 && (buffer.address & 65535u) == 0, "VideoOut: display buffer requires 64 KiB alignment");
require(buffer.tilingMode <= 1, "VideoOut: unsupported display tiling mode");
require(buffer.tilingMode != 0 || buffer.pitchInPixel == 0, "VideoOut: tiled display pitch is unsupported");
require(buffer.dccAddress == 0 || buffer.tilingMode == 0, "VideoOut: a linear display buffer cannot carry DCC metadata");
require(buffer.dccAddress != 0 || buffer.dccClearColor == 0, "VideoOut: a DCC clear color needs DCC metadata");
const auto pitch = buffer.pitchInPixel == 0 ? buffer.width : buffer.pitchInPixel;
require(pitch >= buffer.width && pitch <= 16384, "VideoOut: invalid linear display pitch");
const auto size = buffer.tilingMode == 1 ? static_cast<std::uint64_t>(pitch) * buffer.height * 4u
@ -64,28 +88,35 @@ std::vector<std::byte> DecodeDisplayBuffer(const DisplayBuffer& buffer, std::spa
const auto tiled = buffer.tilingMode == 1 ? (static_cast<std::size_t>(y) * pitch + x) * 4u
: (static_cast<std::size_t>(y / 128u) * blocksPerRow + x / 128u) * 65536u + tileOffset(x, y);
const auto linear = (static_cast<std::size_t>(y) * buffer.width + x) * 4;
if (tenBit) {
std::uint32_t word = 0;
std::memcpy(&word, source.data() + tiled, 4);
const auto red = static_cast<std::byte>((word >> 2u) & 0xffu);
const auto green = static_cast<std::byte>((word >> 12u) & 0xffu);
const auto blue = static_cast<std::byte>((word >> 22u) & 0xffu);
pixels[linear] = rgba ? blue : red;
pixels[linear + 1] = green;
pixels[linear + 2] = rgba ? red : blue;
pixels[linear + 3] = std::byte{0xff};
continue;
}
pixels[linear] = source[tiled + (rgba ? 2 : 0)];
pixels[linear + 1] = source[tiled + 1];
pixels[linear + 2] = source[tiled + (rgba ? 0 : 2)];
pixels[linear + 3] = source[tiled + 3];
decodeTexel(source.data() + tiled, pixels.data() + linear, rgba, tenBit);
}
}
timing.Mark("detile_convert");
return pixels;
}
std::array<std::byte, 4> DisplayBufferClearPixel(const DisplayBuffer& buffer, Graphics::DccKeys keys) {
static_cast<void>(DisplayBufferSize(buffer));
require(buffer.dccAddress != 0, "VideoOut: a display buffer without DCC metadata has no fast-clear value");
std::array<std::byte, 4> texel{};
if (keys == Graphics::DccKeys::ClearRegister) {
if ((buffer.dccClearColor >> 32u) != 0) {
char message[160];
std::snprintf(message, sizeof(message), "VideoOut: DCC register clear color 0x%016llx of display buffer 0x%llx does not fit its 32-bit texel", static_cast<unsigned long long>(buffer.dccClearColor), static_cast<unsigned long long>(buffer.address));
throw std::runtime_error(message);
}
const auto word = static_cast<std::uint32_t>(buffer.dccClearColor);
std::memcpy(texel.data(), &word, texel.size());
} else if (!Graphics::IsDccClear(keys) || !Graphics::FillDccClear(DisplayTexelFormat(buffer.pixelFormat), keys, true, texel)) {
char message[256];
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx (format 0x%016llx) reads %s DCC keys at 0x%llx: only uncompressed keys or a uniform clear code that the display format encodes can be presented", static_cast<unsigned long long>(buffer.address), static_cast<unsigned long long>(buffer.pixelFormat), Graphics::DccKeysName(keys), static_cast<unsigned long long>(buffer.dccAddress));
throw std::runtime_error(message);
}
std::array<std::byte, 4> pixel{};
decodeTexel(texel.data(), pixel.data(), baseFormat(buffer.pixelFormat) == PixelFormatR8G8B8A8, (buffer.pixelFormat & PixelFormatUnormBit) != 0);
return pixel;
}
std::vector<std::byte> ReadDisplayBuffer(const DisplayBuffer& buffer) {
const auto size = DisplayBufferSize(buffer);
static const bool traceGpu = std::getenv("APS5_TRACE_GPU") != nullptr;

View File

@ -62,6 +62,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
for (std::uint32_t i = 0; i < pixel.interpolatorCount; ++i) mix(pixel.interpolatorSettings[i]);
mix(pixel.inputAddr);
for (const bool flag : {pixel.wave32, pixel.hasPerspectiveCenterVgpr, pixel.perspectiveCentroid, pixel.posX, pixel.posY, pixel.posZ, pixel.posW, pixel.frontFace, pixel.ancillary, pixel.sampleShading, pixel.noPerspective, pixel.linearCentroid, pixel.pixelKillEnable, pixel.depthExportEnable, pixel.sampleMaskExportEnable, pixel.earlyZ, pixel.executeOnNoop}) mix(flag);
mix(static_cast<std::uint64_t>(pixel.conservativeZExport));
for (const auto value : pixel.targetOutputMode) mix(value);
for (const auto value : pixel.targetExportMapping) mix(value);
std::lock_guard cacheLock(drawCacheMutex);

View File

@ -81,7 +81,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
if (std::memcmp(&s.viewport, &t.viewport, sizeof(VkViewport)) != 0 || std::memcmp(&s.scissor, &t.scissor, sizeof(VkRect2D)) != 0) return false;
const auto& p = a.pixel;
const auto& q = b.pixel;
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
if (p.interpolatorCount != q.interpolatorCount || p.interpolatorSettings != q.interpolatorSettings || p.wave32 != q.wave32 || p.inputAddr != q.inputAddr || p.hasPerspectiveCenterVgpr != q.hasPerspectiveCenterVgpr || p.perspectiveCentroid != q.perspectiveCentroid || p.posX != q.posX || p.posY != q.posY || p.posZ != q.posZ || p.posW != q.posW || p.frontFace != q.frontFace || p.ancillary != q.ancillary || p.sampleShading != q.sampleShading || p.noPerspective != q.noPerspective || p.linearCentroid != q.linearCentroid || p.pixelKillEnable != q.pixelKillEnable || p.depthExportEnable != q.depthExportEnable || p.sampleMaskExportEnable != q.sampleMaskExportEnable || p.earlyZ != q.earlyZ || p.executeOnNoop != q.executeOnNoop || p.conservativeZExport != q.conservativeZExport || p.targetOutputMode != q.targetOutputMode || p.targetExportMapping != q.targetExportMapping) return false;
if (a.roles != b.roles || a.programs.size() != b.programs.size()) return false;
for (std::size_t i = 0; i < a.programs.size(); ++i) {
const auto& x = a.programs[i];

View File

@ -117,6 +117,17 @@ void Driver::execute(const Submission& submission) {
GuestMemory::SetCurrentPacket(header == FlipPacketHeader ? 0xffffu : opcode, submission.queue);
CaptureTrace::Log("packet submission=%llu queue=%x offset=%zu header=%08x words=%zu", static_cast<unsigned long long>(submission.serial), submission.queue, cursor, header, packet.size());
if (Pm4::Predicated(header) && queue.predication.operation != 0) {
recordQueuedLabelsBeforeRead(submission.queue);
if (!Pm4::PredicationPasses(queue)) {
cursor = opcode == 0x3f ? submission.conditionalEnds.at(cursor) : nextCursor;
continue;
}
}
if (opcode == 0x3f) {
cursor = nextCursor;
continue;
}
const auto flushStart = profilePackets ? std::chrono::steady_clock::now() : std::chrono::steady_clock::time_point{};
flushBetweenPackets(submission.queue, header, opcode == 0x49 || opcode == 0x37);

View File

@ -50,6 +50,19 @@ bool Driver::copySegment(Submission& submission, const std::uint32_t* guest, std
const bool chain = (guest[cursor + 3] & (1u << 20u)) != 0;
require(!chain || guarded.empty(), "a chained INDIRECT_BUFFER inside a conditional execution range is not implemented");
GuestMemory::CheckRange(target, targetWords * sizeof(std::uint32_t), alignof(std::uint32_t));
if (Pm4::Predicated(header)) {
require(!chain, "predicated command buffer chains are not implemented");
const auto start = submission.commands.size();
submission.commands.insert(submission.commands.end(), guest + cursor, guest + cursor + count);
require(!copySegment(submission, target, targetWords, budget), "a REWIND inside a predicated command buffer is not implemented");
for (auto inner = start + count; inner < submission.commands.size(); inner += Pm4::PacketWords(submission.commands[inner])) {
const auto innerHeader = submission.commands[inner];
require(innerHeader != FlipPacketHeader && innerHeader != RenderingWaitPacketHeader, "flips and rendering waits in predicated command buffers are not implemented");
}
submission.conditionalEnds.emplace(start, submission.commands.size());
cursor += count;
continue;
}
if (copySegment(submission, target, targetWords, budget)) {
require(guarded.empty(), "a REWIND inside a conditional execution range is not implemented");
return true;

View File

@ -166,7 +166,7 @@ std::string_view UnsupportedReason(std::uint32_t header) {
}
}
switch (opcode) {
case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x22: case 0x26: case 0x27:
case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x20: case 0x22: case 0x26: case 0x27:
case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x46: case 0x50:
case 0x58: case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a:
case 0x81: case 0x83: case 0x9f: return {};
@ -175,7 +175,6 @@ std::string_view UnsupportedReason(std::uint32_t header) {
return {};
case 0x3a: case 0x8d:
return "graphics draw, shader stages and guest render-target materialization are not implemented";
case 0x20: return "GPU query predication is not implemented";
case 0x33: case 0x3f: return "nested command buffers, branching and nested flip reservation are not implemented";
case 0x3c: case 0x93: return {};
case 0x39:
@ -213,7 +212,7 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
const auto size = [&](std::size_t count) { require(packet.size() == count, "invalid packet size"); };
const auto graphics = [&] { require(queue == 0, "graphics packet in compute queue"); };
const auto reason = UnsupportedReason(header);
if (!reason.empty()) throw std::runtime_error(std::string(reason));
if (!reason.empty() && !(opcode == 0x3f && Predicated(header))) throw std::runtime_error(std::string(reason));
if (opcode == 0x10) {
require((header & 3u) == 0, "unsupported NOP header flags");
switch ((header >> 2u) & 0x3fu) {
@ -239,11 +238,11 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
return;
}
// Header bit 1 selects the compute shader type and bit 2 (register writes) resets the filter CAM;
// neither changes what the packet writes. Predication (bit 0) is not implemented.
// neither changes what the packet writes.
const auto flags = header & 0xffu;
const bool registerWrite = opcode == 0x69 || opcode == 0x76 || opcode == 0x79 || opcode == 0x7a;
auto allowedFlags = opcode == 0x11 ? 2u : registerWrite ? 6u : opcode == 0x3c || opcode == 0x93 ? 2u : 0u;
if (IsTagMarker(packet)) allowedFlags |= 1u;
if (opcode != 0x20 && opcode != 0x22) allowedFlags |= 1u;
if ((flags & ~allowedFlags) != 0) throw std::runtime_error("PM4 header flags 0x" + ToHex(flags) + " are not implemented");
switch (opcode) {
case 0x11:
@ -252,6 +251,17 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
if ((header & 2u) == 0) graphics();
break;
case 0x12: graphics(); size(2); require((packet[1] & ~0xfu) == 0, "unsupported CLEAR_STATE payload bits"); break;
case 0x20: {
graphics();
size(4);
const auto operation = (packet[1] >> 16u) & 7u;
require(operation != 1 && operation != 2, "GPU query predication is not implemented");
require(operation == 0 || operation == 3 || operation == 4, "invalid predication operation");
require((packet[1] & ~0x71100u) == 0, "unsupported SET_PREDICATION bits");
if (operation != 0) require(address(packet[2], packet[3]) != 0 && (packet[2] & 0xfu) == 0, "unaligned predication address");
break;
}
case 0x3f: size(4); break;
case 0x13: case 0x2f: graphics(); size(2); break;
case 0x26: graphics(); size(3); break;
case 0x27:
@ -476,6 +486,16 @@ std::size_t ConditionalWords(std::span<const std::uint32_t> packet) {
return packet[4] & ConditionalWordsMask;
}
bool PredicationPasses(const QueueState& queue) {
const auto& predication = queue.predication;
if (predication.operation == 0) return true;
const std::size_t bytes = predication.operation == 3 ? 8 : 4;
std::uint64_t value = 0;
const GuestMemory::ReadSiteScope site(GuestMemory::ReadSite::Wait);
GuestMemory::Read(predication.address, std::as_writable_bytes(std::span(&value, 1)).first(bytes), bytes);
return (value != 0) == predication.executeWhenSet;
}
std::uint32_t ReadCondition(std::span<const std::uint32_t> packet) {
require(packet.size() == 5 && ((packet[0] >> 8u) & 0xffu) == 0x22u, "expected COND_EXEC packet");
std::uint32_t value = 0;
@ -725,6 +745,12 @@ void Execute(std::span<const std::uint32_t> packet, QueueState& queue) {
APS5_LOG_OUT_DEBUG("CLEAR_STATE targetMaskBefore=0x%x shaderMaskBefore=0x%x", queue.context.contains(0x8e) ? queue.context.at(0x8e) : 0u, queue.context.contains(0x8f) ? queue.context.at(0x8f) : 0u);
queue.ClearContext(); return;
case 0x13: queue.indexBufferSize = packet[1]; return;
case 0x20: {
const auto operation = (packet[1] >> 16u) & 7u;
queue.predication = {operation == 0 ? 0 : address(packet[2], packet[3]), operation, (packet[1] & 0x100u) != 0};
return;
}
case 0x3f: throw std::runtime_error(std::string(UnsupportedReason(packet[0])));
case 0x26: queue.indexBase = address(packet[1], packet[2]); return;
case 0x2a: queue.indexType = packet[1]; return;
case 0x2f: queue.instanceCount = packet[1]; return;

View File

@ -78,6 +78,26 @@ void PresentationScaler::RecordUpload(VkCommandBuffer commands, VkBuffer uploadB
pipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
void PresentationScaler::RecordClear(VkCommandBuffer commands, const VkClearColorValue& color) {
Graphics::Require(sourceImage != VK_NULL_HANDLE, "presentation source image has not been created");
const auto pipelineBarrier = context.Function<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier");
VkImageMemoryBarrier barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = sourceImage;
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
pipelineBarrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
context.Function<PFN_vkCmdClearColorImage>("vkCmdClearColorImage")(commands, sourceImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &barrier.subresourceRange);
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
pipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
void PresentationScaler::RecordBlitInto(VkCommandBuffer commands, VkImage image, VkImageLayout layout, VkFilter filter) {
RecordBlitInto(commands, image, layout, filter, sourceWidth, sourceHeight);
}

View File

@ -5,6 +5,7 @@
#include "prx/libSceAgcDriver/Execution/include/BdaFeatures.hpp"
#include "prx/libSceAgcDriver/Execution/include/PresentationScaler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include "prx/libSceAgcDriver/Execution/include/DisplayFormat.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include "prx/libSceAgcDriver/Graphics/include/GpuColorTransfer.hpp"
@ -1822,6 +1823,41 @@ std::shared_ptr<Graphics::StorageTexture> PresentableResident(const Graphics::Co
return resident;
}
Graphics::DccKeys DisplayKeys(const DisplayBuffer& buffer, std::size_t bytes) {
const auto keys = Graphics::CurrentDccKeys(buffer.dccAddress, bytes);
if (keys != Graphics::DccKeys::Uncompressed && !Graphics::IsDccClear(keys)) {
char message[256];
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads %s DCC keys at 0x%llx: presenting DCC metadata that is not uniformly uncompressed or fast-cleared is not implemented", static_cast<unsigned long long>(buffer.address), Graphics::DccKeysName(keys), static_cast<unsigned long long>(buffer.dccAddress));
throw std::runtime_error(message);
}
return keys;
}
bool ResidentServesDisplay(const Graphics::StorageTexture& resident, const DisplayBuffer& buffer, std::size_t bytes) {
const auto keys = DisplayKeys(buffer, bytes);
if (keys == Graphics::DccKeys::ClearRegister) {
char message[320];
std::snprintf(message, sizeof(message), "VideoOut: display buffer 0x%llx reads register-clear DCC keys at 0x%llx over the pending image 0x%llx (DCC 0x%llx, filled keys %s): whether its results precede the clear is not modeled", static_cast<unsigned long long>(buffer.address), static_cast<unsigned long long>(buffer.dccAddress), static_cast<unsigned long long>(resident.Descriptor().baseAddress), static_cast<unsigned long long>(resident.Descriptor().dccAddress), Graphics::DccKeysName(resident.FilledKeys()));
throw std::runtime_error(message);
}
if (Graphics::IsDccClear(resident.FilledKeys()) && resident.FilledKeys() == keys) return false;
return Graphics::StorageImageServesKeys(resident, buffer.dccAddress);
}
bool ResidentKeysMoved(const Graphics::StorageTexture& resident) {
const auto& own = resident.Descriptor();
if (own.dccAddress == 0) return false;
GuestMemory::CollectWritesUncached(own.dccAddress, Graphics::DccKeyBytes(resident.GuestBytes()));
return Graphics::ProvedClearKeys(own, resident.GuestBytes(), resident.KeyProof()) != resident.UploadedKeys();
}
std::optional<std::array<std::byte, 4>> CompressedClearPixel(const DisplayBuffer& buffer, std::size_t bytes) {
GuestMemory::FlushGpuWrites(buffer.address, bytes);
const auto keys = DisplayKeys(buffer, bytes);
if (keys == Graphics::DccKeys::Uncompressed) return std::nullopt;
return DisplayBufferClearPixel(buffer, keys);
}
// Debug aid: APS5_NO_RESIDENT_PRESENT=1 always presents through guest memory.
bool NoResidentPresent() {
static const bool no = std::getenv("APS5_NO_RESIDENT_PRESENT") != nullptr;
@ -1871,6 +1907,10 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
if (!NoResidentPresent()) {
bool pending = false;
resident = PresentableResident(graphicsContext(), buffer, filter, pending, convert);
if (resident != nullptr && buffer.dccAddress != 0 && !ResidentServesDisplay(*resident, buffer, bytes)) {
resident.reset();
convert = false;
}
if (!pending) {
++notPending;
} else if (resident == nullptr) {
@ -1882,18 +1922,20 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
// walk: the collect memo is per worker packet, so on this thread a memoized answer could
// miss a CPU write that landed after a worker's walk of the same range.
GuestMemory::CollectWritesUncached(buffer.address, bytes);
if (!GuestMemory::UnchangedSince(buffer.address, bytes, resident->Generation())) {
if (!GuestMemory::UnchangedSince(buffer.address, bytes, resident->Generation()) || (buffer.dccAddress != 0 && ResidentKeysMoved(*resident))) {
resident->Refresh();
++refreshedPresents;
}
++residentPresents;
}
}
const auto cleared = buffer.dccAddress != 0 && resident == nullptr ? CompressedClearPixel(buffer, bytes) : std::nullopt;
auto& dumps = Dumps();
bool dumpFrame = false;
if (dumps.dumped < dumps.limit && ++dumps.presents % static_cast<std::uint64_t>(dumps.every) == 0) {
if (dumps.cpu) {
const auto full = ReadDisplayBuffer(buffer);
auto full = cleared ? std::vector<std::byte>(static_cast<std::size_t>(buffer.width) * buffer.height * cleared->size()) : ReadDisplayBuffer(buffer);
for (std::size_t offset = 0; cleared && offset < full.size(); offset += cleared->size()) std::memcpy(full.data() + offset, cleared->data(), cleared->size());
WriteFrameBmp(dumps.dumped++, buffer.width, buffer.height, full, DumpScale());
} else {
state->nextDumpIndex = dumps.dumped++;
@ -1906,7 +1948,12 @@ bool VulkanDevice::PresentDisplayBuffer(const DisplayBuffer& buffer) {
std::fprintf(stderr, "[flip] %llu presents from the resident image (%llu refreshed first), through guest memory: %llu not pending, %llu unsuitable; %llu GPU frame dumps\n", static_cast<unsigned long long>(residentPresents), static_cast<unsigned long long>(refreshedPresents), static_cast<unsigned long long>(notPending), static_cast<unsigned long long>(unsuitable), static_cast<unsigned long long>(gpuDumps));
}
CaptureTrace::Log("present dump=%d address=%llx width=%u height=%u resident=%d generation=%llu", dumpFrame ? state->nextDumpIndex : -1, static_cast<unsigned long long>(buffer.address), buffer.width, buffer.height, resident != nullptr, static_cast<unsigned long long>(resident ? resident->Generation() : 0));
if (!present(buffer.width, buffer.height, true, {}, &buffer, resident, filter, dumpFrame, convert)) {
VkClearColorValue uniform{};
if (cleared) {
const auto channel = [&](std::size_t index) { return static_cast<float>(std::to_integer<unsigned>((*cleared)[index])) / 255.0f; };
uniform = {{channel(2), channel(1), channel(0), channel(3)}};
}
if (!present(buffer.width, buffer.height, true, {}, cleared ? nullptr : &buffer, resident, filter, dumpFrame, convert, cleared ? &uniform : nullptr)) {
// A dropped frame (swapchain out of date) keeps the dump numbering contiguous.
if (dumpFrame) --dumps.dumped;
return false;
@ -1951,7 +1998,7 @@ bool VulkanDevice::AcquireImage() {
return true;
}
bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display, const std::shared_ptr<Graphics::StorageTexture>& resident, VkFilter residentFilter, bool dumpFrame, bool residentConvert) {
bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaque, std::span<const std::byte> pixels, const DisplayBuffer* display, const std::shared_ptr<Graphics::StorageTexture>& resident, VkFilter residentFilter, bool dumpFrame, bool residentConvert, const VkClearColorValue* uniform) {
PerformanceTimer timing("Vulkan.Present");
APS5_LOG_OUT_DEBUG("present begin width=%u height=%u opaque=%u pixels=%zu", width, height, static_cast<unsigned>(opaque), pixels.size());
require(state->swapchain != VK_NULL_HANDLE, "device has no swapchain");
@ -1965,8 +2012,9 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu
const auto index = state->acquiredIndex;
require(index < state->images.size() && index < state->rendered.size(), "acquired image index is out of range");
auto& rendered = state->rendered[index];
const bool clearOnly = pixels.empty() && display == nullptr;
const bool clearOnly = pixels.empty() && display == nullptr && uniform == nullptr;
require(!residentConvert || (resident != nullptr && display != nullptr), "a converted resident presentation needs its image and display buffer");
require(uniform == nullptr || (pixels.empty() && display == nullptr && resident == nullptr), "a uniform presentation has no other source");
const bool direct = resident != nullptr && !residentConvert;
// The scaler's source image, the color transfer's staging and the upload buffer are single
// objects an in-flight blit through them may still read: the paths using or re-creating them
@ -2024,7 +2072,7 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu
auto pipelineBarrier = state->DeviceFunction<PFN_vkCmdPipelineBarrier>("vkCmdPipelineBarrier");
pipelineBarrier(commands, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
Graphics::Recorder::CountBarriers(CommandClass::PresentBlit);
if (pixels.empty() && display == nullptr) {
if (clearOnly) {
APS5_LOG_OUT_DEBUG("Recording swapchain clear opaque=%u image=%p", static_cast<unsigned>(opaque), reinterpret_cast<void*>(barrier.image));
VkClearColorValue clear{};
clear.float32[3] = opaque ? 1.0f : 0.0f;
@ -2056,7 +2104,8 @@ bool VulkanDevice::present(std::uint32_t width, std::uint32_t height, bool opaqu
if (display != nullptr) {
state->colorTransfer->Detile(commands, DisplayRedLow(display->pixelFormat), DisplayTenBit(display->pixelFormat));
}
state->scaler->RecordUpload(commands, display != nullptr ? state->colorTransfer->LinearBuffer() : state->uploadBuffer);
if (uniform != nullptr) state->scaler->RecordClear(commands, *uniform);
else state->scaler->RecordUpload(commands, display != nullptr ? state->colorTransfer->LinearBuffer() : state->uploadBuffer);
}
VkClearColorValue letterbox{};
letterbox.float32[3] = 1.0f;

View File

@ -21,6 +21,7 @@ struct Context;
enum class DccKeys { Uncompressed, Clear0000, Clear0001, Clear1110, Clear1111, ClearRegister, Mixed, Unreadable };
const char* DccKeysName(DccKeys keys);
std::size_t DccKeyBytes(std::uint64_t surfaceBytes);
template<typename ReadFollowed, typename ReadNamed>
bool KeysServeSurface(std::uint64_t followedDcc, DccKeys uploaded, DccKeys filled, std::uint64_t namedDcc, ReadFollowed&& readFollowed, ReadNamed&& readNamed) {

View File

@ -374,6 +374,10 @@ const char* DccKeysName(DccKeys keys) {
return "?";
}
std::size_t DccKeyBytes(std::uint64_t surfaceBytes) {
return static_cast<std::size_t>(surfaceBytes / KeyBytes);
}
namespace {
// ReadDccKeys, saying in `memoized` whether the answer came from the pending-store memo rather

View File

@ -118,8 +118,9 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
const auto shaderControl = read(context, dbShaderControl, RegisterBank::Context);
// Bits 9 and 11 are EXEC_ON_HIER_FAIL and ALPHA_TO_MASK_DISABLE on GFX10; neither changes what the
// recompiled pixel shader computes.
if (((shaderControl >> 13u) & 0x3u) != 0) {
throw std::runtime_error("AGC graphics: DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT is unsupported");
const auto conservativeZExport = (shaderControl >> 13u) & 0x3u;
if (conservativeZExport == 3u) {
throw std::runtime_error("AGC graphics: DB_SHADER_CONTROL.CONSERVATIVE_Z_EXPORT uses the reserved value 3");
}
const auto colFormat = nullProgram ? readOr(context, spiShaderColFormat, RegisterBank::Context, 0u) : read(context, spiShaderColFormat, RegisterBank::Context);
std::array<std::uint8_t, 8> targetOutputMode{};
@ -152,6 +153,7 @@ ShaderRecompiler::ShaderPixelStageInfo DecodePixelStageInfo(const Registers& con
.sampleMaskExportEnable = sampleMaskExportEnable,
.earlyZ = zOrder == 1u && !pixelKillEnable && !depthExportEnable && !sampleMaskExportEnable,
.executeOnNoop = ((shaderControl >> 10u) & 0x1u) != 0,
.conservativeZExport = static_cast<ShaderRecompiler::ConservativeZExport>(conservativeZExport),
.targetOutputMode = targetOutputMode,
.targetExportMapping = exportMappings
};

View File

@ -368,7 +368,7 @@ Module Inspect(const CompiledShader& compiled, const State& state, const VkPhysi
mode(spv::ExecutionModeVertexOrderCw, {});
Require(module.modes.size() == 3, "unsupported tessellation-evaluation execution mode");
} else if (fragment) {
for (const auto& [name, operands] : module.modes) Require(operands.empty() && (name == spv::ExecutionModeOriginUpperLeft || name == spv::ExecutionModeEarlyFragmentTests), "unsupported fragment execution mode");
for (const auto& [name, operands] : module.modes) Require(operands.empty() && (name == spv::ExecutionModeOriginUpperLeft || name == spv::ExecutionModeEarlyFragmentTests || name == spv::ExecutionModeDepthLess || name == spv::ExecutionModeDepthGreater), "unsupported fragment execution mode");
} else Require(module.modes.empty(), "unsupported vertex execution mode");
std::set<std::pair<std::uint32_t, std::uint32_t>> descriptors;
bool push = false;

View File

@ -64,7 +64,7 @@ constexpr std::uint32_t LayerExports = (1u << 18u) | (1u << 19u) | (1u << 21u) |
constexpr std::uint32_t DepthControlMask = ~0x007007f0u;
// EXEC_ON_HIER_FAIL / EXEC_ON_NOOP / EXEC_IF_OVERLAPPED (bits 9, 10, 17) only force the pixel shader
// to run, which it always does here.
constexpr std::uint32_t ShaderControlMask = ~(0x00009870u | 0x00020600u);
constexpr std::uint32_t ShaderControlMask = ~(0x0000f870u | 0x00020600u);
constexpr std::uint32_t AlphaToCoverageMask = ~0x0001ff00u;
constexpr std::uint32_t ScanModeMask = ~2u;
constexpr std::uint32_t ScanControlMask = ~0x06003fffu;

View File

@ -8,6 +8,7 @@
#include <cstdio>
#include <exception>
#include <fstream>
#include <initializer_list>
#include <map>
#include <span>
#include <sstream>
@ -208,6 +209,80 @@ void verifyProgram(const Program& program) {
}
}
const std::vector<std::uint32_t> DwordTableGetpcFirst{
0xbe8c1f00u, 0x800cff0cu, 0x00000058u, 0x820d800du, 0x83928202u, 0xd4c40016u, 0x02000500u, 0x8f128212u, 0xf4000386u, 0x24000000u,
0xbf8cc07fu, 0x7d820003u, 0x808c0e0cu, 0x828d800du, 0xbefd210cu, 0x7e020281u, 0xbf820003u, 0x7e020282u, 0xbf820001u, 0x7e020283u,
0xe0700000u, 0x80000100u, 0xbf810000u, 0x00000020u, 0x00000018u, 0x00000010u};
const std::vector<std::uint32_t> DwordTableIndexFirst{
0x816ac102u, 0x83ea826au, 0x7e020280u, 0x8f6a826au, 0xbe8e1f00u, 0x800ebc0eu, 0x820f800fu, 0xf4000487u, 0xd4000008u, 0x8aea167eu,
0xbf8cc07fu, 0x808e120eu, 0x828f800fu, 0xbefd210eu, 0x7e020281u, 0xbf820003u, 0x7e020282u, 0xbf820001u, 0x7e020283u, 0xe0700000u,
0x80000100u, 0xbf810000u, 0x00000018u, 0x00000010u, 0x00000008u};
const std::vector<std::uint32_t> LongBranchVcc{
0x7e020281u, 0xbeea1f00u, 0x806a906au, 0x826b806bu, 0xbefd216au, 0x7e020282u, 0xe0700000u, 0x80000100u, 0xbf810000u};
const std::vector<std::uint32_t> LongBranchBack{
0xbe880380u, 0x7e020280u, 0x4a020281u, 0x80089008u, 0xbf0ac008u, 0xbf840004u, 0xbe901f00u, 0x80909410u, 0x82918011u, 0xbefd2110u,
0xe0700000u, 0x80000100u, 0xbf810000u};
std::vector<std::uint32_t> patched(std::vector<std::uint32_t> code, std::size_t word, std::initializer_list<std::uint32_t> replacement) {
std::copy(replacement.begin(), replacement.end(), code.begin() + static_cast<std::ptrdiff_t>(word));
return code;
}
std::string refusal(std::span<const std::uint32_t> code) {
try {
static_cast<void>(GraphBuilder{}.Build(RdnaInstructionDecoder{}.Decode(code)));
} catch (const std::exception& error) {
return error.what();
}
return {};
}
void verifyJumpTable(const std::string& name, std::span<const std::uint32_t> code, std::uint32_t loadPc, const std::vector<std::uint64_t>& values, const std::vector<std::uint32_t>& targets) {
const auto graph = GraphBuilder{}.Build(RdnaInstructionDecoder{}.Decode(code));
require(graph.codeTableLoads.size() == 1u && graph.codeTableLoads.front().programCounter == loadPc && graph.codeTableLoads.front().values == values, name + ": wrong code table");
std::vector<std::uint32_t> lowered;
for (const auto& block : graph.blocks) {
if (block.terminator.indirectPcSgpr != InvalidControlFlowId) lowered.insert(lowered.end(), block.terminator.indirectTargetProgramCounters.begin(), block.terminator.indirectTargetProgramCounters.end());
}
std::sort(lowered.begin(), lowered.end());
require(lowered == targets, name + ": the jump does not reach exactly the table targets");
}
void verifyLongBranch(const std::string& name, std::span<const std::uint32_t> code, std::uint32_t branchPc, std::uint32_t target) {
const auto decoded = RdnaInstructionDecoder{}.Decode(code);
const auto graph = GraphBuilder{}.Build(decoded);
const auto source = std::find_if(graph.blocks.begin(), graph.blocks.end(), [&](const BasicBlock& block) { return block.instructionEnd != block.instructionBegin && decoded.instructions[block.instructionEnd - 1u].programCounter == branchPc; });
require(source != graph.blocks.end() && source->terminator.kind == TerminatorKind::Branch && graph.FindBlock(source->terminator.trueBlock).startProgramCounter == target, name + ": the jump does not branch to " + std::to_string(target));
}
void verifyNullSwappc() {
const std::array<std::uint32_t, 2> jump{0xbefd210cu, 0xbf810000u};
const auto swappc = RdnaInstructionDecoder{}.Decode(jump).instructions.front();
require(swappc.op == RdnaOpcode::SSetpcB64 && swappc.opcodeId == 0x21u && swappc.destination.kind == RdnaOperandKind::Null &&
swappc.source0.kind == RdnaOperandKind::ScalarRegister && swappc.source0.reg == 12u, "s_swappc_b64 null, s[12:13] does not decode as s_setpc_b64 s[12:13]");
const std::array<std::uint32_t, 3> front{0x7e020281u, 0xbefd2106u, 0xbf810000u};
require(DecodeRdnaFrontProgram(front).code.size() == 2u, "a front program ending in s_swappc_b64 null, s[6:7] does not end there");
verifyJumpTable("dword jump table after its base", DwordTableGetpcFirst, 0x20u, {0x20u, 0x18u, 0x10u}, {0x3cu, 0x44u, 0x4cu});
verifyJumpTable("dword jump table after its index", DwordTableIndexFirst, 0x1cu, {0x18u, 0x10u, 0x08u}, {0x38u, 0x40u, 0x48u});
verifyLongBranch("long branch through vcc", LongBranchVcc, 0x10u, 0x18u);
verifyLongBranch("backward long branch", LongBranchBack, 0x24u, 0x08u);
const std::array<std::uint32_t, 3> call{0xbe8c1f00u, 0xbe8e210cu, 0xbf810000u};
const std::vector<std::tuple<std::string_view, std::vector<std::uint32_t>, std::string_view>> refused{
{"s_swappc_b64 with a return address", {call.begin(), call.end()}, "unsupported SOP1 opcode 33"},
{"index rewritten after the bound", patched(DwordTableGetpcFirst, 5, {0xbe9203ffu, 0x00000040u}), "unsupported dynamic s_setpc_b64"},
{"index scaled for two-dword entries", patched(DwordTableGetpcFirst, 7, {0x8f128312u}), "unsupported dynamic s_setpc_b64"},
{"entry rewritten after the load", patched(DwordTableGetpcFirst, 11, {0xbe8e0380u}), "unsupported dynamic s_setpc_b64"},
{"base rewritten after the load", patched(DwordTableGetpcFirst, 11, {0xbe8d0380u}), "unsupported dynamic s_setpc_b64"},
{"borrow other than zero", patched(DwordTableGetpcFirst, 13, {0x828d810du}), "unsupported dynamic s_setpc_b64"},
{"long branch with a register high half", patched(LongBranchVcc, 3, {0x826b046bu}), "unsupported dynamic s_setpc_b64"},
{"branch into a long branch", patched(LongBranchVcc, 0, {0xbf850001u}), "does not start in its block"},
};
for (const auto& [name, code, message] : refused) {
const auto error = refusal(code);
require(error.find(message) != std::string::npos, std::string(name) + ": expected a refusal with '" + std::string(message) + "', got '" + error + "'");
}
}
}
int main(int argc, char** argv) {
@ -656,6 +731,92 @@ test:
{0xbe880380u, 0x7e020280u, 0xbf06c002u, 0xbf840006u, 0x4a020081u, 0xe0700000u, 0x80000100u, 0xbf810000u, 0x4a020282u, 0x80089008u,
0xbf0ac008u, 0xbf85fffcu, 0xe0700000u, 0x80000100u, 0xbf810000u},
Split::None},
{"dword jump table after its base", R"(
s_getpc_b64 s[12:13]
base:
s_add_u32 s12, s12, table - base
s_addc_u32 s13, s13, 0
s_min_u32 s18, s2, 2
v_cmp_gt_u32 s[22:23], v0, s2
s_lshl_b32 s18, s18, 2
s_load_dword s14, s[12:13], s18
s_waitcnt lgkmcnt(0)
v_cmp_lt_u32 vcc, s3, v0
s_sub_u32 s12, s12, s14
s_subb_u32 s13, s13, 0
s_swappc_b64 null, s[12:13]
case0:
v_mov_b32 v1, 1
s_branch done
case1:
v_mov_b32 v1, 2
s_branch done
case2:
v_mov_b32 v1, 3
done:
buffer_store_dword v1, off, s[0:3], 0
s_endpgm
table:
.long table - case0, table - case1, table - case2)",
DwordTableGetpcFirst, Split::None},
{"dword jump table after its index", R"(
s_add_i32 vcc_lo, s2, -1
s_min_u32 vcc_lo, vcc_lo, 2
v_mov_b32 v1, 0
s_lshl_b32 vcc_lo, vcc_lo, 2
s_getpc_b64 s[14:15]
base:
s_add_u32 s14, s14, table - base - 8
s_addc_u32 s15, s15, 0
s_load_dword s18, s[14:15], vcc_lo offset:8
s_andn2_b64 vcc, exec, s[22:23]
s_waitcnt lgkmcnt(0)
s_sub_u32 s14, s14, s18
s_subb_u32 s15, s15, 0
s_swappc_b64 null, s[14:15]
case0:
v_mov_b32 v1, 1
s_branch done
case1:
v_mov_b32 v1, 2
s_branch done
case2:
v_mov_b32 v1, 3
done:
buffer_store_dword v1, off, s[0:3], 0
s_endpgm
table:
.long table - 8 - case0, table - 8 - case1, table - 8 - case2)",
DwordTableIndexFirst, Split::None},
{"long branch through vcc", R"(
v_mov_b32 v1, 1
s_getpc_b64 vcc
base:
s_add_u32 vcc_lo, vcc_lo, target - base
s_addc_u32 vcc_hi, vcc_hi, 0
s_swappc_b64 null, vcc
v_mov_b32 v1, 2
target:
buffer_store_dword v1, off, s[0:3], 0
s_endpgm)",
LongBranchVcc, Split::None},
{"backward long branch", R"(
s_mov_b32 s8, 0
v_mov_b32 v1, 0
loop:
v_add_nc_u32 v1, 1, v1
s_add_u32 s8, s8, 16
s_cmp_lt_u32 s8, 64
s_cbranch_scc0 done
s_getpc_b64 s[16:17]
base:
s_sub_u32 s16, s16, base - loop
s_subb_u32 s17, s17, 0
s_swappc_b64 null, s[16:17]
done:
buffer_store_dword v1, off, s[0:3], 0
s_endpgm)",
LongBranchBack, Split::None},
};
int failures = 0;
for (const auto& program : programs) {
@ -666,6 +827,12 @@ test:
++failures;
}
}
try {
verifyNullSwappc();
} catch (const std::exception& error) {
std::fprintf(stderr, "%s\n", error.what());
++failures;
}
if (failures != 0) return 1;
std::puts("Control flow structurization tests passed");
return 0;

View File

@ -0,0 +1,83 @@
#include "prx/libSceAgcDriver/Execution/include/DisplayBuffer.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DccMetadata.hpp"
#include <array>
#include <cstdint>
#include <cstdio>
#include <stdexcept>
#include <string>
using AgcDriver::DisplayBuffer;
using AgcDriver::Graphics::DccKeys;
namespace {
int failures = 0;
void Expect(bool condition, const std::string& what) {
if (condition) return;
std::fprintf(stderr, "FAIL: %s\n", what.c_str());
++failures;
}
template<typename TAction>
std::string Rejection(TAction action) {
try {
action();
} catch (const std::exception& error) {
return error.what();
}
return {};
}
void ExpectPixels(const DisplayBuffer& buffer, DccKeys keys, std::array<unsigned, 4> expected, const char* what) {
const auto pixel = AgcDriver::DisplayBufferClearPixel(buffer, keys);
for (std::size_t i = 0; i < pixel.size(); ++i) {
if (std::to_integer<unsigned>(pixel[i]) == expected[i]) continue;
Expect(false, std::string(what) + ": byte " + std::to_string(i) + " is " + std::to_string(std::to_integer<unsigned>(pixel[i])) + ", expected " + std::to_string(expected[i]));
return;
}
}
}
int main() {
constexpr std::uint64_t bgra = 0x8000000000000000ull;
constexpr std::uint64_t rgba = 0x8000000022000000ull;
constexpr std::uint64_t tenBit = 0x0100000000000000ull;
DisplayBuffer buffer{65536, bgra, 131, 3, 0, 0, 0x7f0000, 0x11223344};
ExpectPixels(buffer, DccKeys::Clear0000, {0, 0, 0, 0}, "8-bit 0000");
ExpectPixels(buffer, DccKeys::Clear0001, {0, 0, 0, 255}, "8-bit 0001");
ExpectPixels(buffer, DccKeys::Clear1110, {255, 255, 255, 0}, "8-bit 1110");
ExpectPixels(buffer, DccKeys::Clear1111, {255, 255, 255, 255}, "8-bit 1111");
ExpectPixels(buffer, DccKeys::ClearRegister, {0x44, 0x33, 0x22, 0x11}, "B8G8R8A8 register clear");
buffer.pixelFormat = rgba;
ExpectPixels(buffer, DccKeys::ClearRegister, {0x22, 0x33, 0x44, 0x11}, "R8G8B8A8 register clear");
ExpectPixels(buffer, DccKeys::Clear0001, {0, 0, 0, 255}, "R8G8B8A8 0001");
buffer.pixelFormat = rgba | tenBit;
ExpectPixels(buffer, DccKeys::Clear1110, {255, 255, 255, 255}, "A2B10G10R10 1110");
ExpectPixels(buffer, DccKeys::Clear0001, {0, 0, 0, 255}, "A2B10G10R10 0001");
buffer.dccClearColor = 0x3ff;
ExpectPixels(buffer, DccKeys::ClearRegister, {0, 0, 255, 255}, "A2B10G10R10 register clear");
buffer.pixelFormat = bgra | tenBit;
ExpectPixels(buffer, DccKeys::ClearRegister, {255, 0, 0, 255}, "A2R10G10B10 register clear");
for (const auto keys : {DccKeys::Uncompressed, DccKeys::Mixed, DccKeys::Unreadable}) {
const auto message = Rejection([&] { AgcDriver::DisplayBufferClearPixel(buffer, keys); });
Expect(message.find(AgcDriver::Graphics::DccKeysName(keys)) != std::string::npos && message.find("0x7f0000") != std::string::npos, std::string("keys without a clear value were presented as one: ") + AgcDriver::Graphics::DccKeysName(keys));
}
buffer.dccClearColor = 0x100000000ull;
Expect(Rejection([&] { AgcDriver::DisplayBufferClearPixel(buffer, DccKeys::ClearRegister); }).find("register clear color") != std::string::npos, "a register clear color wider than the texel was truncated");
buffer.dccClearColor = 0;
buffer.dccAddress = 0;
Expect(!Rejection([&] { AgcDriver::DisplayBufferClearPixel(buffer, DccKeys::Clear0000); }).empty(), "a buffer without DCC metadata was presented as fast-cleared");
buffer.dccClearColor = 1;
Expect(!Rejection([&] { AgcDriver::DisplayBufferSize(buffer); }).empty(), "a DCC clear color without metadata was accepted");
buffer.dccClearColor = 0;
buffer.dccAddress = 0x7f0000;
buffer.tilingMode = 1;
Expect(!Rejection([&] { AgcDriver::DisplayBufferSize(buffer); }).empty(), "a linear display buffer with DCC metadata was accepted");
buffer.tilingMode = 0;
Expect(AgcDriver::DisplayBufferSize(buffer) == 2 * 65536, "DCC metadata changed the display footprint");
if (failures != 0) return 1;
std::puts("display buffer DCC tests passed");
return 0;
}

View File

@ -6,6 +6,7 @@
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
#include "ControlFlow/RequestSerializer.hpp"
#include "CacheKey.hpp"
#include <spirv/unified1/spirv.hpp>
#include <algorithm>
#include <array>
@ -1283,6 +1284,7 @@ void misalignedShaderDataTests() {
struct ModuleShape {
bool fragment = false;
std::optional<std::uint32_t> fragmentMode;
bool push = false;
std::uint32_t pushLength = 32;
std::uint32_t pushStride = 4;
@ -1446,6 +1448,7 @@ std::vector<std::uint32_t> makeModule(const ModuleShape& shape) {
else emit(words, spv::OpEntryPoint, {shape.fragment ? spv::ExecutionModelFragment : spv::ExecutionModelVertex, main, 0x6e69616du, 0, output});
words[entryPointOffset] += static_cast<std::uint32_t>(extraInterface.size()) << 16u;
words.insert(words.end(), extraInterface.begin(), extraInterface.end());
if (shape.fragmentMode) emit(words, spv::OpExecutionMode, {main, *shape.fragmentMode});
if (shape.fragment) emit(words, spv::OpExecutionMode, {main, spv::ExecutionModeOriginUpperLeft});
words.insert(words.end(), annotations.begin(), annotations.end());
words.insert(words.end(), declarations.begin(), declarations.end());
@ -1563,6 +1566,81 @@ std::vector<LocatedInput> locatedInputs(std::span<const std::uint32_t> words) {
return result;
}
constexpr std::array<std::uint32_t, 3> DepthExportCode{0xf8001881u, 2u, 0xbf810000u};
AgcDriver::QueueState depthExportQueue(std::uint32_t shaderControl) {
auto queue = makeState();
queue.context[0x1b3] = 0x402u;
queue.context[0x1b4] = 0x402u;
queue.context[0x203] = shaderControl;
return queue;
}
ShaderRecompiler::RecompileRequest depthExportRequest(std::uint32_t shaderControl) {
const auto queue = depthExportQueue(shaderControl);
ShaderRecompiler::RecompileRequest request{};
request.shader = {ShaderRecompiler::ShaderStage::Fragment, 0x30000u, DepthExportCode, 0, {}};
request.context.waveSize = 64;
request.context.pixel = AgcDriver::Graphics::DecodePixelStageInfo(queue.context, IdentityExports);
request.target.vulkanVersion = 0x00401000u;
request.target.spirvVersion = 0x00010300u;
request.target.subgroupSize = 64;
request.layout.pushConstantSizeBytes = 128;
request.useCache = false;
return request;
}
std::set<std::uint32_t> depthModes(const ShaderRecompiler::RecompileRequest& request) {
const auto result = ShaderRecompiler::Recompile(request);
const auto& words = result.spirv.Words();
std::set<std::uint32_t> modes;
for (std::size_t at = 5; at < words.size() && (words[at] >> 16u) != 0; at += words[at] >> 16u) {
if (static_cast<spv::Op>(words[at] & 0xffffu) != spv::OpExecutionMode) continue;
const auto mode = words[at + 2];
if (mode == spv::ExecutionModeDepthReplacing || mode == spv::ExecutionModeDepthGreater || mode == spv::ExecutionModeDepthLess || mode == spv::ExecutionModeDepthUnchanged) modes.insert(mode);
}
return modes;
}
void conservativeZExportTests() {
const std::array<std::pair<std::uint32_t, std::set<std::uint32_t>>, 4> cases{{
{0x0021u, {spv::ExecutionModeDepthReplacing}},
{0x2021u, {spv::ExecutionModeDepthReplacing, spv::ExecutionModeDepthLess}},
{0x4021u, {spv::ExecutionModeDepthReplacing, spv::ExecutionModeDepthGreater}},
{0x2020u, {}}
}};
const ShaderRecompiler::RequestSerializer serializer;
std::set<std::vector<std::uint64_t>> keys;
for (const auto& [shaderControl, expected] : cases) {
const auto request = depthExportRequest(shaderControl);
const auto replayed = serializer.Deserialize(serializer.Serialize(request));
Require(depthModes(request) == expected && depthModes(replayed.request) == expected, "DB_SHADER_CONTROL " + std::to_string(shaderControl) + " declared the wrong depth execution modes");
std::vector<std::uint64_t> key;
ShaderRecompiler::RecompileCacheKey::Build(request, key);
keys.insert(key);
}
Require(keys.size() == cases.size(), "the recompile cache key ignores CONSERVATIVE_Z_EXPORT");
for (const auto shaderControl : {0x2020u, 0x4020u}) {
const auto queue = depthExportQueue(shaderControl);
static_cast<void>(AgcDriver::Graphics::DecodeState(queue));
Require(AgcDriver::Graphics::DrawRejection(queue, false).empty(), "the precheck rejected DB_SHADER_CONTROL " + std::to_string(shaderControl));
}
expectFailure([] { static_cast<void>(depthExportRequest(0x6021u)); }, "CONSERVATIVE_Z_EXPORT");
AgcDriver::Graphics::State state{};
state.stages.path = AgcDriver::Graphics::ShaderPath::Vertex;
ShaderRecompiler::RecompileResult vertex;
vertex.spirv = makeModule({});
ShaderRecompiler::RecompileResult pixel;
const std::array<AgcDriver::Graphics::CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0}, {ShaderRecompiler::ShaderStage::Fragment, &pixel, 0}}};
const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
for (const auto mode : {spv::ExecutionModeDepthLess, spv::ExecutionModeDepthGreater}) {
pixel.spirv = makeModule({.fragment = true, .fragmentMode = mode});
AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false);
}
pixel.spirv = makeModule({.fragment = true, .fragmentMode = spv::ExecutionModeDepthUnchanged});
expectFailure([&] { AgcDriver::Graphics::ValidateShaders(shaders, state, subgroup, false); }, "unsupported fragment execution mode");
}
void pixelParameterSlotTests() {
using AgcDriver::Graphics::CompiledShader;
const VkPhysicalDeviceSubgroupProperties subgroup{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES};
@ -1922,6 +2000,7 @@ int main() {
DepthClipTests();
DepthStencilTests();
DepthBoundsBiasTests();
conservativeZExportTests();
DisabledColorTests();
CompactedExportTests();
metadataPassTests();

View File

@ -180,7 +180,7 @@ void testAutoDraw() {
}
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3}), 0); }, "packet size");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2, 0}), 0); }, "packet size");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 1), 0); }, "header flags");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 2), 0); }, "header flags");
state.userConfig[0x24a] = std::numeric_limits<std::uint32_t>::max();
check(AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {1, 2}), state).firstVertex == std::numeric_limits<std::uint32_t>::max(), "last vertex rejected");
expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {2, 2}), state); }, "vertex range overflow");
@ -508,7 +508,7 @@ void testEventWrite() {
for (const auto bit : {0x40u, 0x80u, 0x800u, 0x80000000u}) {
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410u | bit}), 0); }, "reserved bits");
}
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410}, 1), 0); }, "header flags");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410}, 2), 0); }, "header flags");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x410, 0, 0}), 0); }, "packet size");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x13a, 0, 0}), 0); }, "event type 58");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x46, {0x0d}), 0); }, "event type 13");
@ -541,7 +541,7 @@ void testAcquireMem() {
packet[index] = value;
expectFailure([&] { AgcDriver::Pm4::Validate(packet, 0); }, reason);
};
invalidWord(0, captured[0] | 1u, "header flags");
invalidWord(0, captured[0] | 2u, "header flags");
invalidWord(1, 4, "control flags");
invalidWord(1, 0x00800000, "control flags");
invalidWord(3, 1, "above 40 bits");
@ -554,6 +554,50 @@ void testAcquireMem() {
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x58, {0, 0, 0, 0, 0, 0, 0, 0}), 0); }, "packet size");
}
void testPredication() {
alignas(16) std::uint64_t flag[4] = {0, 0, 0, 0};
auto* flag32 = reinterpret_cast<std::uint32_t*>(&flag[2]);
const auto setPredication = [&](std::uint32_t operation, bool executeWhenSet, const void* address) {
return makePacket(0x20, {(operation << 16u) | (executeWhenSet ? 0x100u : 0u) | 0x1000u, low(address), high(address)});
};
AgcDriver::QueueState state;
check(AgcDriver::Pm4::PredicationPasses(state), "inactive predication must pass");
execute(state, setPredication(3, true, &flag[0]));
check(state.predication.operation == 3 && state.predication.executeWhenSet && state.predication.address == reinterpret_cast<std::uintptr_t>(&flag[0]), "BOOL64 predication state mismatch");
check(!AgcDriver::Pm4::PredicationPasses(state), "zero BOOL64 value must skip draw-visible packets");
flag[0] = 1ull << 40u;
check(AgcDriver::Pm4::PredicationPasses(state), "upper BOOL64 bits must count");
execute(state, setPredication(3, false, &flag[0]));
check(!AgcDriver::Pm4::PredicationPasses(state), "non-zero BOOL64 value must skip draw-not-visible packets");
flag[0] = 0;
check(AgcDriver::Pm4::PredicationPasses(state), "zero BOOL64 value must run draw-not-visible packets");
execute(state, setPredication(4, true, &flag[2]));
flag[2] = 0xffffffff00000000ull;
check(*flag32 == 0 && !AgcDriver::Pm4::PredicationPasses(state), "BOOL32 must read only 32 bits");
*flag32 = 7;
check(AgcDriver::Pm4::PredicationPasses(state), "non-zero BOOL32 value must run draw-visible packets");
execute(state, setPredication(0, false, nullptr));
check(state.predication.operation == 0 && AgcDriver::Pm4::PredicationPasses(state), "clear must end predication");
expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(1, true, &flag[0]), 0); }, "query predication");
expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(2, true, &flag[0]), 0); }, "query predication");
expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(5, true, &flag[0]), 0); }, "invalid predication operation");
expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(3, true, &flag[0]), 0x20); }, "compute queue");
expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(3, true, nullptr), 0); }, "unaligned predication address");
expectFailure([&] { AgcDriver::Pm4::Validate(setPredication(3, true, reinterpret_cast<const std::uint8_t*>(&flag[0]) + 8), 0); }, "unaligned predication address");
auto continued = setPredication(3, true, &flag[0]);
continued[1] |= 1u << 31u;
expectFailure([&] { AgcDriver::Pm4::Validate(continued, 0); }, "SET_PREDICATION bits");
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x20, {3u << 16u, low(&flag[0]), high(&flag[0])}, 1), 0); }, "header flags");
AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 1), 0);
AgcDriver::Pm4::Validate(makePacket(0x46, {0x410}, 1), 0);
const auto call = makePacket(0x3f, {0x1000, 0, 0x0f200010}, 1);
AgcDriver::Pm4::Validate(call, 0);
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x3f, {0x1000, 0, 0x0f200010}), 0); }, "nested command buffers");
expectFailure([&] { AgcDriver::Pm4::Execute(call, state); }, "nested command buffers");
}
void testDriverSubmission() {
std::array<std::uint32_t, 2> source{0x10, 73};
std::array<std::uint32_t, 1> destination{};
@ -693,6 +737,49 @@ void testConditionalSubmission() {
check(results[13] == 0, "a rejected conditional submission executed a guarded packet");
}
void testPredicatedSubmission() {
alignas(16) std::uint64_t flag[2] = {0, 0};
alignas(16) std::array<std::uint32_t, 4> written{};
alignas(16) std::array<std::uint32_t, 1> nestedWritten{};
const auto nested = makePacket(0x37, {0x100, low(nestedWritten.data()), high(nestedWritten.data()), 3});
const auto call = makePacket(0x3f, {low(nested.data()), high(nested.data()), 0x0f200000u | static_cast<std::uint32_t>(nested.size())}, 1);
const auto submit = [](std::vector<std::uint32_t>& commands) {
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
check(sceAgcDriverSubmitDcb(&packet) == 0, "predicated submission failed");
AgcDriverWaitIdle_nid_postfix();
};
std::vector<std::uint32_t> commands;
for (const auto& packet : {
makePacket(0x20, {0x31100, low(&flag[0]), high(&flag[0])}),
makePacket(0x37, {0x100, low(&written[0]), high(&written[0]), 7}, 1),
call,
makePacket(0x20, {0, 0, 0}),
makePacket(0x37, {0x100, low(&written[1]), high(&written[1]), 9}, 1)
}) commands.insert(commands.end(), packet.begin(), packet.end());
submit(commands);
check(written[0] == 0, "packet with a false predicate was executed");
check(nestedWritten[0] == 0, "command buffer with a false predicate was executed");
check(written[1] == 9, "cleared predication skipped a packet");
flag[0] = 1;
commands.clear();
for (const auto& packet : {
makePacket(0x20, {0x31100, low(&flag[0]), high(&flag[0])}),
makePacket(0x37, {0x100, low(&written[2]), high(&written[2]), 5}, 1),
call,
makePacket(0x20, {0x31000, low(&flag[0]), high(&flag[0])}),
makePacket(0x37, {0x100, low(&written[3]), high(&written[3]), 6}, 1),
makePacket(0x20, {0, 0, 0})
}) commands.insert(commands.end(), packet.begin(), packet.end());
submit(commands);
check(written[2] == 5, "packet with a true predicate was skipped");
check(nestedWritten[0] == 3, "command buffer with a true predicate was skipped");
check(written[3] == 0, "draw-not-visible packet ran with a set value");
auto chain = makePacket(0x3f, {low(nested.data()), high(nested.data()), 0x0f300000u | static_cast<std::uint32_t>(nested.size())}, 1);
Packet chained{chain.data(), static_cast<std::uint32_t>(chain.size()), 0, {}};
expectFailure([&] { sceAgcDriverSubmitDcb(&chained); }, "predicated command buffer chains");
AgcDriverWaitIdle_nid_postfix();
}
void testAsyncMemoryFailure() {
auto commands = makePacket(0x37, {0x100, 0x1000, 0, 1});
Packet packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
@ -728,7 +815,9 @@ int main(int argc, char** argv) {
testConditionReadSynchronization();
testEventWrite();
testAcquireMem();
testPredication();
testDriverSubmission();
testPredicatedSubmission();
testConditionalSubmission();
LibcRunShutdown_nid_postfix();
std::puts("PM4 catalog, registers, state, memory, conditional execution and submission tests passed");

View File

@ -76,7 +76,7 @@ void testValidation() {
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); });
commands[0] = 0xc001105c;
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); });
commands[0] = 0xc0017601;
commands[0] = 0xc0017608;
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); });
commands[0] = 0xc0017600;
commands[1] = 0x10000;
@ -110,7 +110,7 @@ void testClearState() {
words[0] = 0xc0011200;
packet.dw_num = 3;
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); });
words[0] = 0xc0001201;
words[0] = 0xc0001202;
packet.dw_num = 2;
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); });
words[0] = 0xc0001200;

View File

@ -9,7 +9,6 @@
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>
@ -18,52 +17,94 @@ namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 64;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t Results = 8;
constexpr std::uint32_t Results = 16;
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
alignas(256) constexpr std::array<std::uint32_t, 26> DotCode{
0x34020084, 0x34060085, 0xe0301000, 0x80000401, 0xe0301004, 0x80000501, 0xe0301008, 0x80000601,
0xbf8c3f70, 0xcc13400a, 0x1c1a0b04, 0xcc13480b, 0x141a0b04, 0xcc13420c, 0xbc1a0b04, 0xcc13c00d,
0x1c1a0b04, 0xe0701000, 0x80010a03, 0xe0701004, 0x80010b03, 0xe0701008, 0x80010c03, 0xe070100c,
0x80010d03, 0xbf810000,
alignas(256) constexpr std::array<std::uint32_t, 37> Code{
0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0x7e140306, 0x04140b04, 0xcc13400b,
0x1c1a0b04, 0xcc13420c, 0xbc1a0b04, 0xcc13410d, 0x5c1a0b04, 0xcc13480e, 0x141a0b04, 0xcc13c00f,
0x1c1a0b04, 0x7e200306, 0x04200af2, 0x7e220306, 0x04220aff, 0x40003c00, 0x7e240306, 0x04240afa,
0xff00b104, 0x7e260306, 0xcc135013, 0x0c1a0b04, 0xe0781000, 0x80010a03, 0xe0781010, 0x80010e03,
0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000,
};
constexpr std::array<std::array<std::uint32_t, 3>, 8> Edges{{
{0x3c003c00u, 0x3c003c00u, 0x00000000u}, {0x7c000000u, 0x3c000000u, 0x3f800000u}, {0x7c003c00u, 0x00003c00u, 0x3f800000u},
{0xfc007c00u, 0x3c003c00u, 0x40000000u}, {0x80000000u, 0x00008000u, 0x80000000u}, {0x7bff7bffu, 0x7bff7bffu, 0xcf800000u},
{0x38003800u, 0xb800b800u, 0x3e800000u}, {0x7e003c00u, 0x3c003c00u, 0x00000000u},
}};
constexpr std::uint32_t Rows[32][4] = {
{0x0001bc00u, 0x81bfacbau, 0xc541aa2fu, 0x00000000u},
{0x01f61a79u, 0x3c000381u, 0xc1729fffu, 0x00000000u},
{0x02c9be80u, 0xb252213du, 0x41880526u, 0x00000000u},
{0x032f030cu, 0x0d88ca10u, 0x3f800000u, 0x00000000u},
{0x04003848u, 0x2427b61cu, 0x3920d8c0u, 0x00000000u},
{0x04008190u, 0x01c85a38u, 0xc1e5655cu, 0x00000000u},
{0x00ba3faeu, 0x02bcc61fu, 0x439ff26au, 0x00000000u},
{0x00bc0400u, 0xaed23800u, 0xaf78b10fu, 0x00000000u},
{0x47a37c85u, 0x7ceb0000u, 0x80521c95u, 0x00000000u},
{0x6bc64fd7u, 0x02f45f4du, 0xffc8ab86u, 0x00000000u},
{0x034102d0u, 0xac427d00u, 0x7fa00000u, 0x00000000u},
{0x00000000u, 0xf35dfc00u, 0x805c769au, 0x00000000u},
{0x0000030cu, 0x8370c506u, 0x00000001u, 0x00000000u},
{0x00002783u, 0x5f2c0896u, 0x7f800000u, 0x00000000u},
{0x00010001u, 0xfc00004cu, 0xca63705au, 0x00000000u},
{0x64ba817bu, 0x80000000u, 0x00000001u, 0x00000000u},
{0x000083ffu, 0x7d00def6u, 0x95a99701u, 0x00000000u},
{0x7c210c47u, 0x825c8000u, 0x45b66cc3u, 0x00000000u},
{0x00003134u, 0x8000c266u, 0x8029d2f0u, 0x00000000u},
{0x3b898000u, 0x0000e8b9u, 0x0034ccdcu, 0x00000000u},
{0x00000000u, 0x04008000u, 0x5f834da7u, 0x00000000u},
{0x00000001u, 0x366e51ddu, 0x7f7fffffu, 0x00000000u},
{0x00000001u, 0xdc472c9du, 0x42750eeeu, 0x00000000u},
{0x000000c8u, 0x83ff3c00u, 0x43380747u, 0x00000000u},
{0x0000010cu, 0x7c00f56fu, 0x005df72bu, 0x00000000u},
{0x00000377u, 0xdaabea88u, 0xf540cddbu, 0x00000000u},
{0x000003cau, 0x5b9f7e00u, 0xcaba68d5u, 0x00000000u},
{0x00000400u, 0x785b8ab8u, 0x7fa00000u, 0x00000000u},
{0x0000051cu, 0x6f115c7fu, 0x0047beccu, 0x00000000u},
{0x00000c55u, 0x3c00ec7fu, 0x263e4496u, 0x00000000u},
{0x00000f28u, 0x9e2d0123u, 0x565f49ebu, 0x00000000u},
{0x00001209u, 0x3fe3c10fu, 0x801b0c3eu, 0x00000000u}
};
float Half(std::uint32_t bits) {
const std::uint32_t sign = (bits & 0x8000u) << 16u;
const std::uint32_t exponent = (bits >> 10u) & 0x1fu;
const std::uint32_t mantissa = bits & 0x3ffu;
if (exponent == 0x1fu) return std::bit_cast<float>(sign | 0x7f800000u | (mantissa << 13u));
if (exponent == 0u) return std::bit_cast<float>(sign) + (sign != 0u ? -1.0f : 1.0f) * std::ldexp(static_cast<float>(mantissa), -24);
return std::bit_cast<float>(sign | ((exponent + 112u) << 23u) | (mantissa << 13u));
}
constexpr std::uint32_t Expected[32][10] = {
{0xc541a901u, 0xc541a901u, 0x4541a901u, 0xc541ab5du, 0xc541aa2fu, 0xc541a901u, 0xc541ab5eu, 0xc541ab5eu, 0xc541aa30u, 0xc541aa2fu},
{0xc1729fdfu, 0xc1729fdfu, 0x41729fdfu, 0xc172a01fu, 0xc172930du, 0xc1729fdfu, 0xc1729fc7u, 0xc1529fc7u, 0xc172a037u, 0xc172930du},
{0x4187e315u, 0x4187e315u, 0xc187e315u, 0x41882737u, 0x418a9676u, 0x4187e315u, 0x41881a1au, 0x4184f11au, 0x41880521u, 0x418a9676u},
{0x3f7fdb0fu, 0x3f7fdb0fu, 0xbf7fdb0fu, 0x3f801279u, 0x3f7fd966u, 0x3f7fdb0fu, 0xc1320000u, 0xc131fd3cu, 0x41a5a000u, 0x3f7fd966u},
{0xbe511687u, 0xbe511687u, 0x3e511687u, 0x3e5166f4u, 0x3c105963u, 0xbe511687u, 0xbec36be5u, 0xbeb2cfe5u, 0x392b6e40u, 0x3c105963u},
{0xc1e56f13u, 0xc1e56f13u, 0x41e56f13u, 0xc1e55ba5u, 0xc1e54c7cu, 0xc1e56f13u, 0x432a5354u, 0x432a5358u, 0x429ba4a9u, 0xc1e54c7cu},
{0x439a1228u, 0x439a1228u, 0xc39a1228u, 0x43a5d2acu, 0x439ff26au, 0x439a1228u, 0x439ce2eau, 0x439ce2edu, 0x439ff25eu, 0x439ff26au},
{0x37f5fb14u, 0x37f5fb14u, 0xb7f5fb14u, 0xb7f5fc0cu, 0xb5720f8bu, 0x37f5fb14u, 0x3f000000u, 0x3e92e000u, 0x3f75bfecu, 0xb5720f8bu},
{0x7fd0a000u, 0x7fd0a000u, 0xffd0a000u, 0x7fd0a000u, 0x7fd0a000u, 0x7fd0a000u, 0x7fdd6000u, 0x7fdd6000u, 0x7fdd6000u, 0x7fd0a000u},
{0xffc8ab86u, 0xffc8ab86u, 0x7fc8ab86u, 0xffc8ab86u, 0xffc8ab86u, 0xffc8ab86u, 0xffc8ab86u, 0xffc8ab86u, 0x7fd0a000u, 0xffc8ab86u},
{0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0xffe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u},
{0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xffc00000u},
{0xb974e480u, 0xb974e480u, 0x3974e480u, 0x3974e480u, 0xb1279400u, 0xb974e480u, 0xc0a0c000u, 0xc0a0c0dcu, 0xbe16f048u, 0xb1279400u},
{0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u},
{0xff800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0xffc00000u, 0xff800000u, 0xff800000u, 0xff800000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u},
{0x7fe00000u, 0x7fe00000u, 0xffe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fc42000u, 0x7fe00000u},
{0x7fc42000u, 0x7fc42000u, 0x7fc42000u, 0xffc42000u, 0x7fc42000u, 0x7fc42000u, 0x45b66cc3u, 0x45b66cc3u, 0x45b66cc3u, 0x7fc42000u},
{0xbf052ae0u, 0xbf052ae0u, 0x3f052ae0u, 0x3f052ae0u, 0x80000000u, 0xbf052ae0u, 0xc04cc000u, 0xc04cc000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xc50e5804u, 0x00000000u, 0xc5172000u, 0xc5172000u, 0xc3c494a0u, 0xc50e5804u},
{0x5f834da7u, 0x5f834da7u, 0xdf834da7u, 0x5f834da7u, 0x5f834da7u, 0x5f834da7u, 0x5f834da7u, 0x5f834da7u, 0x5f834da7u, 0x5f834da7u},
{0x7f7fffffu, 0x7f7fffffu, 0xff7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0x7f7fffffu, 0x7f7fffffu},
{0x42750eeeu, 0x42750eeeu, 0xc2750eeeu, 0x42750eeeu, 0x42750eeau, 0x42750eeeu, 0x427558beu, 0xc3f314e8u, 0x42750eeeu, 0x42750eeau},
{0x43380748u, 0x43380748u, 0xc3380748u, 0x43380746u, 0x43380747u, 0x43380748u, 0x43390747u, 0x4339073fu, 0x43380747u, 0x43380747u},
{0xffc00000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7f800000u},
{0xf540cddbu, 0xf540cddbu, 0x7540cddbu, 0xf540cddbu, 0xf540cddbu, 0xf540cddbu, 0xf540cddbu, 0xf540cddbu, 0xf540cddbu, 0xf540cddbu},
{0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u},
{0x7fe00000u, 0x7fe00000u, 0xffe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u, 0x7fe00000u},
{0x3cb7c720u, 0x3cb7c720u, 0xbcb7c720u, 0xbcb7c720u, 0x3f106b70u, 0x3cb7c720u, 0x438fe000u, 0x46669f00u, 0x3d9bd158u, 0x3f106b70u},
{0xbf9bd158u, 0xbf9bd158u, 0x3f9bd158u, 0x3f9bd158u, 0x398aa000u, 0xbf9bd158u, 0xc58fe000u, 0xc58fd000u, 0xbeb7c720u, 0x398aa000u},
{0x565f49ebu, 0x565f49ebu, 0xd65f49ebu, 0x565f49ebu, 0x565f49ebu, 0x565f49ebu, 0x565f49ebu, 0x565f49ebu, 0x565f49ebu, 0x565f49ebu},
{0xbaf43c38u, 0xbaf43c38u, 0x3af43c38u, 0x3af43c38u, 0x3abe63ecu, 0xbaf43c38u, 0xc021e000u, 0x3fb50000u, 0xba90cd60u, 0x3abe63ecu}
};
std::uint32_t RandomHalf(std::uint64_t& state) {
state = state * 6364136223846793005ull + 1442695040888963407ull;
const auto bits = static_cast<std::uint32_t>(state >> 33u);
return (bits & 0x8000u) | ((8u + (bits >> 16u) % 15u) << 10u) | (bits & 0x3ffu);
}
constexpr const char* Names[10] = {"v_dot2c", "v_dot2", "v_dot2 neg_lo src0/src2 neg_hi src1", "v_dot2 neg_lo src1 neg_hi src0", "v_dot2 op_sel src0", "v_dot2 clamp", "v_dot2c inline 1.0", "v_dot2c literal", "v_dot2c dpp quad_perm", "v_dot2 op_sel src1"};
void FillInput() {
std::uint64_t state = 0x9e3779b97f4a7c15ull;
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
auto* words = &Input[tid * Inputs];
if (tid < Edges.size()) {
std::copy(Edges[tid].begin(), Edges[tid].end(), words);
continue;
}
words[0] = RandomHalf(state) | (RandomHalf(state) << 16u);
words[1] = RandomHalf(state) | (RandomHalf(state) << 16u);
words[2] = std::bit_cast<std::uint32_t>(Half(RandomHalf(state)) * Half(RandomHalf(state)));
}
void Fill(std::uint32_t tid, std::uint32_t* words) {
for (std::uint32_t i = 0; i < 4; ++i) words[i] = Rows[tid][i];
}
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
@ -77,6 +118,10 @@ std::string Hex(std::uint32_t value) {
return text;
}
void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) {
Require(actual == expected, std::string("v_dot2_f32_f16: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected));
}
auto Compile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code) {
std::vector<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
@ -84,7 +129,7 @@ auto Compile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> cod
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{32, 0, userData, compute, std::nullopt, std::nullopt, memory},
@ -95,14 +140,6 @@ auto Compile(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> cod
return ShaderRecompiler::Recompile(request);
}
void Run(AgcDriver::VulkanDevice& device) {
Output.fill(0xdeadbeefu);
const std::span<const std::uint32_t> code(DotCode);
const auto result = Compile(device, code);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, const std::string& what) {
alignas(256) const std::array<std::uint32_t, 3> code{word0, 0x1c1a0b04u, 0xbf810000u};
std::string refusal;
@ -114,30 +151,19 @@ void CheckRefused(AgcDriver::VulkanDevice& device, std::uint32_t word0, const st
Require(refusal.find("v_dot2_f32_f16 accumulator op_sel, op_sel_hi and neg_hi are not implemented") != std::string::npos, what + " was not refused");
}
float Dot(float aLow, float aHigh, float bLow, float bHigh, float c) {
return std::fma(aHigh, bHigh, std::fma(aLow, bLow, c));
}
void Expect(std::uint32_t tid, std::uint32_t actual, float expected, const char* name) {
const std::uint32_t bits = std::bit_cast<std::uint32_t>(expected);
const bool nan = std::isnan(expected) && std::isnan(std::bit_cast<float>(actual));
Require(nan || actual == bits, std::string("v_dot2_f32_f16: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(bits));
void Run(AgcDriver::VulkanDevice& device) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]);
Output.fill(0xdeadbeefu);
const std::span<const std::uint32_t> code(Code);
const auto result = Compile(device, code);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void Check() {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto* in = &Input[tid * Inputs];
const auto* out = &Output[tid * Results];
const float aLow = Half(in[0] & 0xffffu);
const float aHigh = Half(in[0] >> 16u);
const float bLow = Half(in[1] & 0xffffu);
const float bHigh = Half(in[1] >> 16u);
const float c = std::bit_cast<float>(in[2]);
const float plain = Dot(aLow, aHigh, bLow, bHigh, c);
Expect(tid, out[0], plain, "plain");
Expect(tid, out[1], Dot(aHigh, aLow, bLow, bHigh, c), "op_sel swapping the halves of src0");
Expect(tid, out[2], Dot(-aLow, aHigh, bLow, -bHigh, -c), "neg_lo src0, neg_hi src1 and neg src2");
if (!std::isnan(plain)) Expect(tid, out[3], plain > 0.0f ? std::min(plain, 1.0f) : 0.0f, "clamp");
const std::uint32_t* out = &Output[tid * Results];
for (std::uint32_t i = 0; i < 10; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]);
}
}
@ -147,7 +173,6 @@ int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
FillInput();
Run(*device);
Check();
CheckRefused(*device, 0xcc13600au, "op_sel on the accumulator");

View File

@ -0,0 +1,192 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#endif
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 64;
constexpr std::size_t BlockBytes = 65536;
constexpr std::uint32_t InputDwords = 768;
constexpr std::uint32_t Fill = 0xcdcdcdcdu;
constexpr std::uint32_t VectorBase = 512;
constexpr std::uint32_t DwordBase = 4;
constexpr std::uint32_t AddtidStoreBase = 256;
constexpr std::uint32_t AddtidBase = 384;
constexpr std::uint32_t AtomicBase = 448;
constexpr std::uint32_t OutputBase = 1024;
constexpr std::uint32_t OutputDwords = 8;
alignas(256) constexpr std::array<std::uint32_t, 22> VccBaseCode{
0xbeea0400, 0x34020082, 0x34040084, 0x340c0085, 0x4a0c0cff, 0x00001000, 0x4ad404ff, 0x00000048,
0xdc308010, 0x046a0001, 0xdcc98700, 0x0c6a0201, 0xdc3887b8, 0x006a006a, 0xbf8c3f70, 0xdc788000,
0x006a0006, 0xdc708010, 0x006a0406, 0xdc708014, 0x006a0c06, 0xbf810000,
};
alignas(256) constexpr std::array<std::uint32_t, 12> VccAddtidCode{
0xbeea0400, 0x340c0085, 0x4a0c0cff, 0x00001000, 0xdc588600, 0x056a0000, 0xbf8c3f70, 0xdc708018,
0x006a0506, 0xdc5c8400, 0x006a0500, 0xbf810000,
};
class GuestBlock {
public:
GuestBlock() {
#ifdef _WIN32
block = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, BlockBytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
#else
block = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, BlockBytes));
#endif
Require(block != nullptr, "global vcc base: cannot allocate the guest block");
GuestAllocations::Mutation().Add(block, BlockBytes, true, true);
}
~GuestBlock() {
GuestAllocations::Mutation().Remove(block);
#ifdef _WIN32
VirtualFree(block, 0, MEM_RELEASE);
#else
std::free(block);
#endif
}
GuestBlock(const GuestBlock&) = delete;
GuestBlock& operator=(const GuestBlock&) = delete;
std::uint8_t* Data() { return block; }
private:
std::uint8_t* block = nullptr;
};
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::vector<std::uint32_t> Initial() {
std::vector<std::uint32_t> memory(BlockBytes / 4u, Fill);
for (std::uint32_t dword = 0; dword < InputDwords; ++dword) memory[dword] = 0x51000000u + dword * 0x00010203u;
return memory;
}
std::vector<std::uint32_t> ExpectedBase() {
const auto initial = Initial();
auto memory = initial;
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
auto* out = &memory[OutputBase + tid * OutputDwords];
for (std::uint32_t component = 0; component < 4u; ++component) out[component] = initial[VectorBase + tid * 4u + component];
out[4] = initial[DwordBase + tid];
out[5] = initial[AtomicBase + tid];
memory[AtomicBase + tid] = initial[AtomicBase + tid] + tid * 16u;
}
return memory;
}
std::vector<std::uint32_t> ExpectedAddtid(std::uint32_t waveSize) {
const auto initial = Initial();
auto memory = initial;
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const std::uint32_t lane = tid % waveSize;
memory[OutputBase + tid * OutputDwords + 6u] = initial[AddtidBase + lane];
memory[AddtidStoreBase + lane] = initial[AddtidBase + lane];
}
return memory;
}
ShaderRecompiler::RecompileResult Recompile(std::span<const std::uint32_t> code, std::uint32_t waveSize, std::span<const std::uint32_t> userData, const ShaderRecompiler::SpirvTarget& target) {
const std::array<ShaderRecompiler::MemoryRegion, 1> regions{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, regions},
target,
{0, 0, 0, 128}
};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}
void Run(AgcDriver::VulkanDevice& device, GuestBlock& guest, std::span<const std::uint32_t> code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target, const std::vector<std::uint32_t>& expected, const std::string& name) {
auto memory = Initial();
std::memcpy(guest.Data(), memory.data(), BlockBytes);
const auto address = static_cast<std::uint64_t>(reinterpret_cast<std::uintptr_t>(guest.Data()));
std::vector<std::uint32_t> userData(2, 0u);
userData[0] = static_cast<std::uint32_t>(address);
userData[1] = static_cast<std::uint32_t>(address >> 32u);
const auto result = Recompile(code, waveSize, userData, target);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
std::memcpy(memory.data(), guest.Data(), BlockBytes);
for (std::size_t dword = 0; dword < memory.size(); ++dword) {
Require(memory[dword] == expected[dword], "global vcc base: " + name + " byte " + std::to_string(dword * 4u) + " is " + Hex(memory[dword]) + ", expected " + Hex(expected[dword]));
}
}
void CheckRejected(const AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, const std::string& reason) {
const std::vector<std::uint32_t> userData(2, 0u);
std::string failure;
try {
static_cast<void>(Recompile(code, 32, userData, device.Target()));
} catch (const std::exception& error) {
failure = error.what();
}
Require(failure.find(reason) != std::string::npos, "global vcc base: expected '" + reason + "', got '" + failure + "'");
}
void CheckRejections(const AgcDriver::VulkanDevice& device) {
alignas(256) static constexpr std::array<std::uint32_t, 3> vccHiLoad{0xdc308000, 0x046b0001, 0xbf810000};
alignas(256) static constexpr std::array<std::uint32_t, 3> m0Atomic{0xdcc88000, 0x007c0201, 0xbf810000};
alignas(256) static constexpr std::array<std::uint32_t, 3> execStore{0xdc708000, 0x007e0201, 0xbf810000};
alignas(256) static constexpr std::array<std::uint32_t, 3> vccHiAddtid{0xdc588000, 0x056b0000, 0xbf810000};
CheckRejected(device, vccHiLoad, "FLAT scalar address register range overflow");
CheckRejected(device, m0Atomic, "FLAT scalar address register range overflow");
CheckRejected(device, execStore, "FLAT scalar address register range overflow");
CheckRejected(device, vccHiAddtid, "global_load_dword_addtid supports only an SGPR pair as base address");
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
CheckRejections(*device);
GuestBlock guest;
const auto base = ExpectedBase();
Run(*device, guest, VccBaseCode, 32, device->Target(), base, "wave32");
Run(*device, guest, VccBaseCode, 64, device->Target(), base, "wave64");
Run(*device, guest, VccBaseCode, 64, device->ComputeTarget(32), base, "wave64 split");
if (device->Target().subgroupSize < 32u) {
std::printf("addtid cases skipped, the device's subgroups are narrower than a wave (%u lanes)\n", device->Target().subgroupSize);
} else {
Run(*device, guest, VccAddtidCode, 32, device->Target(), ExpectedAddtid(32), "addtid wave32");
Run(*device, guest, VccAddtidCode, 64, device->Target(), ExpectedAddtid(64), "addtid wave64");
Run(*device, guest, VccAddtidCode, 64, device->ComputeTarget(32), ExpectedAddtid(64), "addtid wave64 split");
}
std::puts("global vcc base tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -0,0 +1,143 @@
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include "Recompiler.hpp"
#include "VulkanTestDevice.hpp"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <span>
#include <string>
#include <vector>
namespace {
using AgcDriver::Graphics::Require;
using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t MaxThreads = 64;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t Results = 4;
alignas(256) std::array<std::uint32_t, MaxThreads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, MaxThreads * Results> Output{};
alignas(256) constexpr std::array<std::uint32_t, 109> Wave32Code{
0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e1402ffu, 0xdeadbeefu, 0x7e1602ffu, 0xdeadbeefu,
0x7e1802ffu, 0xdeadbeefu, 0x7e1a02ffu, 0xdeadbeefu, 0xbe940380u, 0xd7600008u, 0x02002904u, 0xd760000au,
0x02002905u, 0xbe8c1f00u, 0x800cff0cu, 0x0000014cu, 0x820d800du, 0xbe89030au, 0x83928308u, 0xd4c40016u,
0x02001104u, 0x8f128212u, 0xf4000386u, 0x24000000u, 0xbf8cc07fu, 0x7d820a0au, 0x808c0e0cu, 0x828d800du,
0xbefd210cu, 0x8009ff0au, 0x11110000u, 0xbf820008u, 0x8009ff0au, 0x22220000u, 0xbf820005u, 0x8009ff0au,
0x33330000u, 0xbf820002u, 0x8009ff0au, 0x44440000u, 0xd761000au, 0x02002809u, 0x816ac108u, 0x83ea836au,
0x8f6a826au, 0xbe8e1f00u, 0x800eff0eu, 0x000000d4u, 0x820f800fu, 0xf4000487u, 0xd4000008u, 0x8aea167eu,
0xbf8cc07fu, 0x808e120eu, 0x828f800fu, 0xbefd210eu, 0x8009ff0au, 0x0a000000u, 0xbf820008u, 0x8009ff0au,
0x0b000000u, 0xbf820005u, 0x8009ff0au, 0x0c000000u, 0xbf820002u, 0x8009ff0au, 0x0d000000u, 0xd761000bu,
0x02002809u, 0x8009ff0au, 0x600d0000u, 0xbeea1f00u, 0x806a946au, 0x826b806bu, 0xbefd216au, 0xbe8903ffu,
0x00000badu, 0xd761000cu, 0x02002809u, 0xbe890380u, 0xbe950383u, 0x80090a09u, 0x80958115u, 0xbf068015u,
0xbf850004u, 0xbe901f00u, 0x80909410u, 0x82918011u, 0xbefd2110u, 0xd761000du, 0x02002809u, 0x80148114u,
0xbf0aa014u, 0xbf85ffabu, 0xe0781000u, 0x80010a01u, 0xbf810000u, 0x00000110u, 0x00000104u, 0x000000f8u,
0x000000ecu, 0x000000acu, 0x000000a0u, 0x00000094u, 0x00000088u,
};
alignas(256) constexpr std::array<std::uint32_t, 109> Wave64Code{
0x34020084u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e1402ffu, 0xdeadbeefu, 0x7e1602ffu, 0xdeadbeefu,
0x7e1802ffu, 0xdeadbeefu, 0x7e1a02ffu, 0xdeadbeefu, 0xbe940380u, 0xd7600008u, 0x02002904u, 0xd760000au,
0x02002905u, 0xbe8c1f00u, 0x800cff0cu, 0x0000014cu, 0x820d800du, 0xbe89030au, 0x83928308u, 0xd4c40016u,
0x02001104u, 0x8f128212u, 0xf4000386u, 0x24000000u, 0xbf8cc07fu, 0x7d820a0au, 0x808c0e0cu, 0x828d800du,
0xbefd210cu, 0x8009ff0au, 0x11110000u, 0xbf820008u, 0x8009ff0au, 0x22220000u, 0xbf820005u, 0x8009ff0au,
0x33330000u, 0xbf820002u, 0x8009ff0au, 0x44440000u, 0xd761000au, 0x02002809u, 0x816ac108u, 0x83ea836au,
0x8f6a826au, 0xbe8e1f00u, 0x800eff0eu, 0x000000d4u, 0x820f800fu, 0xf4000487u, 0xd4000008u, 0x8aea167eu,
0xbf8cc07fu, 0x808e120eu, 0x828f800fu, 0xbefd210eu, 0x8009ff0au, 0x0a000000u, 0xbf820008u, 0x8009ff0au,
0x0b000000u, 0xbf820005u, 0x8009ff0au, 0x0c000000u, 0xbf820002u, 0x8009ff0au, 0x0d000000u, 0xd761000bu,
0x02002809u, 0x8009ff0au, 0x600d0000u, 0xbeea1f00u, 0x806a946au, 0x826b806bu, 0xbefd216au, 0xbe8903ffu,
0x00000badu, 0xd761000cu, 0x02002809u, 0xbe890380u, 0xbe950383u, 0x80090a09u, 0x80958115u, 0xbf068015u,
0xbf850004u, 0xbe901f00u, 0x80909410u, 0x82918011u, 0xbefd2110u, 0xd761000du, 0x02002809u, 0x80148114u,
0xbf0ac014u, 0xbf85ffabu, 0xe0781000u, 0x80010a01u, 0xbf810000u, 0x00000110u, 0x00000104u, 0x000000f8u,
0x000000ecu, 0x000000acu, 0x000000a0u, 0x00000094u, 0x00000088u,
};
constexpr std::array<std::uint32_t, 8> Selectors{0u, 1u, 2u, 3u, 4u, 7u, 0x80000000u, 0xffffffffu};
constexpr std::array<std::uint32_t, 4> GetpcFirstCases{0x11110000u, 0x22220000u, 0x33330000u, 0x44440000u};
constexpr std::array<std::uint32_t, 4> IndexFirstCases{0x0a000000u, 0x0b000000u, 0x0c000000u, 0x0d000000u};
constexpr const char* Names[Results] = {"dword_table_getpc_first", "dword_table_index_first", "long_branch_vcc", "long_branch_back"};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu), bytes, 0x01016facu};
}
std::string Hex(std::uint32_t value) {
char text[16];
std::snprintf(text, sizeof(text), "0x%08x", value);
return text;
}
std::uint32_t Expected(std::uint32_t lane, std::uint32_t result) {
const auto selector = Input[lane * Inputs];
const auto value = Input[lane * Inputs + 1u];
switch (result) {
case 0: return value + GetpcFirstCases[std::min(selector, 3u)];
case 1: return value + IndexFirstCases[std::min(selector - 1u, 3u)];
case 2: return value + 0x600d0000u;
default: return value * 3u;
}
}
ShaderRecompiler::RecompileResult Compile(std::span<const std::uint32_t> code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) {
std::vector<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(code.data()), std::as_bytes(code)}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{waveSize, 1, 1}, 0u, {false, false, false}, false, 1};
ShaderRecompiler::RecompileRequest request{
{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(code.data()), code, 0, {}},
{waveSize, 0, userData, compute, std::nullopt, std::nullopt, memory},
target,
{0, 0, 0, 128}
};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}
void Run(AgcDriver::VulkanDevice& device, std::span<const std::uint32_t> code, std::uint32_t waveSize, const ShaderRecompiler::SpirvTarget& target) {
for (std::uint32_t tid = 0; tid < MaxThreads; ++tid) {
Input[tid * Inputs] = Selectors[tid % Selectors.size()];
Input[tid * Inputs + 1u] = 0x9e3779b9u * (tid + 1u);
}
Output.fill(0xdeadbeefu);
const auto result = Compile(code, waveSize, target);
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
device.WaitIdle();
}
void Check(std::uint32_t lanes) {
for (std::uint32_t tid = 0; tid < lanes; ++tid) {
for (std::uint32_t index = 0; index < Results; ++index) {
const auto actual = Output[tid * Results + index];
const auto expected = Expected(tid, index);
Require(actual == expected, "swappc wave" + std::to_string(lanes) + ": lane " + std::to_string(tid) + " " + Names[index] + " is " + Hex(actual) + ", expected " + Hex(expected));
}
}
}
}
int main() {
try {
const auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
Run(*device, Wave32Code, 32, device->ComputeTarget(32));
Check(32);
Run(*device, Wave64Code, 64, device->Target());
Check(64);
Run(*device, Wave64Code, 64, device->ComputeTarget(32));
Check(64);
std::puts("swappc tests passed");
return 0;
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
}

View File

@ -14,13 +14,6 @@ static void AjmStub(const char* name) {
extern "C" {
int APS5_VABI sceAjmBatchCancel(uint32_t context, uint32_t batch) {
(void)context;
(void)batch;
AjmStub(__func__);
return 0;
}
int APS5_VABI sceAjmBatchJobControl(AjmBatchInfo* info, uint32_t instance, uint64_t flags, const void* sideband_input, size_t sideband_input_size, void* sideband_output, size_t sideband_output_size) {
(void)info;
(void)instance;

View File

@ -26,6 +26,7 @@ extern "C" {
// memory follow the SDK layouts.
namespace {
constexpr int SCE_AJM_ERROR_INVALID_CONTEXT = static_cast<int>(0x80930002);
constexpr int SCE_AJM_ERROR_INVALID_INSTANCE = static_cast<int>(0x80930003);
constexpr int SCE_AJM_ERROR_INVALID_BATCH = static_cast<int>(0x80930004);
constexpr int SCE_AJM_ERROR_INVALID_PARAMETER = static_cast<int>(0x80930005);
@ -492,6 +493,134 @@ bool ParseMp3Frame(const std::uint8_t* data, std::size_t size, Mp3Frame& frame)
return true;
}
constexpr std::uint32_t MP3_OFL_NONE = 0;
constexpr std::uint32_t MP3_OFL_LAME = 1;
constexpr std::uint32_t MP3_OFL_VBRI = 2;
constexpr std::uint32_t MP3_OFL_FGH = 3;
constexpr std::uint32_t MP3_OFL_VBRI_AND_FGH = 4;
std::uint32_t ReadBigEndian(const std::uint8_t* data, std::size_t bytes) {
std::uint32_t value = 0;
for (std::size_t i = 0; i < bytes; ++i) value = (value << 8u) | data[i];
return value;
}
int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame);
void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint32_t header, AjmDecMp3ParseFrame* frame) {
const std::size_t end = std::min<std::size_t>(streamSize, frame->frame_size);
if (end <= 4) return;
const std::size_t available = end - 4;
std::size_t bit = 0;
const auto readBits = [&](std::uint32_t count) {
std::uint32_t value = 0;
for (std::uint32_t i = 0; i < count; ++i, ++bit) value = (value << 1u) | (bit / 8 < available ? (stream[4 + bit / 8] >> (7 - bit % 8)) & 1u : 0u);
return value;
};
const bool mpeg1 = ((header >> 19u) & 3u) == 3u;
const bool mono = ((header >> 6u) & 3u) == 3u;
bit += mpeg1 ? (mono ? 18 : 20) : (mono ? 9 : 10);
std::uint32_t mainDataBits = 0;
for (std::uint32_t granule = 0; granule < (mpeg1 ? 2u : 1u); ++granule) {
for (std::uint32_t channel = 0; channel < frame->num_channels; ++channel) {
mainDataBits += readBits(12);
bit += mpeg1 ? 47 : 51;
}
}
bit += mainDataBits;
std::size_t at = 4 + std::min<std::size_t>((bit + 7) / 8, available);
if (at + 8 <= end && (std::memcmp(stream + at, "Xing", 4) == 0 || std::memcmp(stream + at, "Info", 4) == 0)) {
const std::uint8_t flags = stream[at + 7];
std::size_t field = at + 8;
std::uint32_t frames = 0;
if (flags & 0x01u) {
if (field + 4 > end) return;
frames = ReadBigEndian(stream + field, 4);
frame->num_frames = frames;
field += 4;
}
if (flags & 0x02u) {
field += 4;
if (field > end) return;
}
if (flags & 0x04u) {
field += 100;
if (field > end) return;
}
if (flags & 0x08u) {
field += 4;
if (field > end) return;
}
if (field + 0x18 <= end && std::memcmp(stream + field, "LAME", 4) == 0) {
const std::uint32_t delay = (std::uint32_t{stream[field + 0x15]} << 4u) | (stream[field + 0x16] >> 4u);
const std::uint32_t padding = ((std::uint32_t{stream[field + 0x16]} & 0x0Fu) << 8u) | stream[field + 0x17];
if ((flags & 0x01u) && frames > 0) frame->total_samples = frames * frame->samples_per_channel - (delay + padding);
frame->encoder_delay = frame->samples_per_channel + delay + 529;
frame->ofl_type = MP3_OFL_LAME;
}
} else if (at != 36 && end >= 40 && std::memcmp(stream + 36, "VBRI", 4) == 0) {
NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (VBRI header outside MPEG-1 stereo)");
} else if (at + 26 <= end && std::memcmp(stream + at, "VBRI", 4) == 0) {
frame->encoder_delay = ReadBigEndian(stream + at + 6, 2);
frame->ofl_type = MP3_OFL_VBRI;
if (frame->frame_size <= streamSize) {
AjmDecMp3ParseFrame next{};
if (ParseMp3Header(stream + frame->frame_size, streamSize - static_cast<std::uint32_t>(frame->frame_size), 1, &next) == 0 && next.ofl_type == MP3_OFL_FGH) {
frame->encoder_delay += next.encoder_delay;
frame->total_samples = next.total_samples;
frame->ofl_type = MP3_OFL_VBRI_AND_FGH;
}
}
} else if (at + 10 <= end) {
while (at + 9 < end && stream[at] != 0xB4u) ++at;
if (at + 9 >= end) return;
std::uint8_t crc = 0xFF;
for (std::size_t i = 0; i < 9; ++i) {
for (int shift = 7; shift >= 0; --shift) {
const bool same = ((crc >> 7u) & 1u) == ((stream[at + i] >> shift) & 1u);
crc = static_cast<std::uint8_t>(same ? (crc << 1u) ^ 0x45u : crc << 1u);
}
}
if (stream[at + 9] != crc) return;
frame->encoder_delay = ReadBigEndian(stream + at + 1, 2);
frame->total_samples = ReadBigEndian(stream + at + 3, 4);
frame->ofl_type = MP3_OFL_FGH;
}
}
int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame) {
if (!stream || streamSize < 4 || !frame) return SCE_AJM_ERROR_INVALID_PARAMETER;
static constexpr std::uint32_t sampleRates[4][4] = {{11025, 12000, 8000, 0}, {0, 0, 0, 0}, {22050, 24000, 16000, 0}, {44100, 48000, 32000, 0}};
static constexpr std::uint32_t kbps[4][16] = {
{0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0},
};
const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3];
const std::uint32_t version = (header >> 19u) & 3u;
const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u];
const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u;
const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0;
AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate);
if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER;
if (parseOfl && ((header >> 17u) & 3u) != 1u) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup outside layer III)");
if (parseOfl && ((header >> 16u) & 1u) == 0) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup in a CRC-protected frame)");
const bool mpeg1 = version == 3;
frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u);
frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u;
frame->samples_per_channel = mpeg1 ? 1152u : 576u;
frame->bitrate = bitrate;
frame->sample_rate = sampleRate;
frame->encoder_delay = 0;
frame->num_frames = 0;
frame->total_samples = 0;
frame->ofl_type = MP3_OFL_NONE;
if (parseOfl) ParseMp3Ofl(stream, streamSize, header, frame);
return 0;
}
void OpenMp3(Instance& instance) {
static const bool quiet = (av_log_set_level(TraceEnabled() ? AV_LOG_WARNING : AV_LOG_QUIET), true);
(void)quiet;
@ -870,33 +999,7 @@ int APS5_VABI sceAjmDecAt9ParseConfigData(const void* config_data, AjmDecAt9Conf
}
int APS5_VABI sceAjmDecMp3ParseFrame(const uint8_t* stream, uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame) {
if (!stream || streamSize < 4 || !frame) return SCE_AJM_ERROR_INVALID_PARAMETER;
static constexpr std::uint32_t sampleRates[4][4] = {{11025, 12000, 8000, 0}, {0, 0, 0, 0}, {22050, 24000, 16000, 0}, {44100, 48000, 32000, 0}};
static constexpr std::uint32_t kbps[4][16] = {
{0, 8, 16, 24, 32, 40, 48, 56, 64, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0},
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0},
};
const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3];
const std::uint32_t version = (header >> 19u) & 3u;
const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u];
const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u;
const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0;
AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate);
if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER;
if (parseOfl) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup)");
const bool mpeg1 = version == 3;
frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u);
frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u;
frame->samples_per_channel = mpeg1 ? 1152u : 576u;
frame->bitrate = bitrate;
frame->sample_rate = sampleRate;
frame->encoder_delay = 0;
frame->num_frames = 0;
frame->total_samples = 0;
frame->ofl_type = 0;
return 0;
return ParseMp3Header(stream, streamSize, parseOfl, frame);
}
int APS5_VABI sceAjmBatchInitialize(void* buffer, size_t size, AjmBatchInfo* info) {
@ -1009,6 +1112,12 @@ int APS5_VABI sceAjmBatchWait(uint32_t context, uint32_t batch, uint32_t timeout
return 0;
}
int APS5_VABI sceAjmBatchCancel(uint32_t context, uint32_t batch) {
if (context == 0 || context >= g_nextContext.load(std::memory_order_relaxed)) return SCE_AJM_ERROR_INVALID_CONTEXT;
std::lock_guard lock(g_batchLock);
return g_batches.contains(batch) ? 0 : SCE_AJM_ERROR_INVALID_BATCH;
}
int APS5_VABI sceAjmBatchErrorDump(const AjmBatchInfo* info, AjmBatchError* error) {
(void)info;
if (error) std::memset(error, 0, sizeof(*error));

View File

@ -4,6 +4,7 @@
#include "prx/libc/include/General.hpp"
#include "prx/libkernel/Apr/include/AprCommandBuffer.hpp"
#include <cstring>
#include <stdexcept>
static constexpr int SCE_AMPR_ERROR_BUFFER_FULL = 0x8002001C;
static constexpr int SCE_KERNEL_ERROR_EINVAL = 0x80020016;
@ -35,6 +36,57 @@ static bool ValidWait(std::uint32_t compare, std::uint32_t flush) {
return compare < 4u && flush < 2u;
}
static bool ValidWaitOnCounter_04_00(std::uint32_t access, std::uint32_t compare, std::uint32_t maskOperation, std::uint32_t flush) {
return access < 8u && compare <= 6u && maskOperation < 2u && flush < 2u;
}
static bool ValidWriteCounter_04_00(std::uint32_t counter, std::uint32_t access, std::uint32_t operation) {
return (counter & 0x80u) == 0u && access < 8u && operation < 5u;
}
static constexpr std::uint64_t MeasureInvalid = static_cast<std::uint32_t>(SCE_KERNEL_ERROR_EINVAL);
static bool ValidReadLength(std::uint64_t length) {
return length != 0u && length <= 0x100000000ull;
}
static bool ValidReadOffset(std::uint64_t offset) {
return offset < 0x10000000000ull;
}
static bool ValidRead(const void* destination, std::uint64_t length, std::uint64_t offset) {
constexpr std::uint64_t userLimit = 0xF00000000000ull;
const auto address = reinterpret_cast<std::uint64_t>(destination);
return ValidReadLength(length) && address <= userLimit && length <= userLimit - address && ValidReadOffset(offset);
}
static int AppendRead(Apr::CommandBufferObject* buffer, Apr::Opcode opcode, std::uint32_t fileId, const void* destination, std::uint64_t size, std::uint64_t offset) {
if (!buffer) return SCE_KERNEL_ERROR_EINVAL;
if (opcode != Apr::Opcode::ReadFile && (buffer->recording & Apr::ScatterGatherValid) == 0u) return SCE_KERNEL_ERROR_EINVAL;
Apr::ReadFileCommand command{{opcode, sizeof(command)}, fileId, 0, reinterpret_cast<std::uint64_t>(destination), size, offset};
const int result = Append(buffer, &command, sizeof(command));
if (result == 0) buffer->recording |= Apr::ScatterGatherValid;
return result;
}
static std::uint64_t NopBytes(std::uint32_t dwords) {
return sizeof(Apr::CommandHeader) + std::uint64_t{dwords} * 4u;
}
static int AppendNop(Apr::CommandBufferObject* buffer, std::uint32_t dwords, const std::uint32_t* data) {
if (!buffer) return SCE_KERNEL_ERROR_EINVAL;
const std::uint64_t bytes = NopBytes(dwords);
if (!buffer->base || bytes > buffer->size - buffer->offset) return SCE_AMPR_ERROR_BUFFER_FULL;
const Apr::CommandHeader header{Apr::Opcode::Nop, static_cast<std::uint32_t>(bytes)};
std::uint8_t* destination = buffer->base + buffer->offset;
std::memset(destination, 0, bytes);
std::memcpy(destination, &header, sizeof(header));
if (data) std::memcpy(destination + sizeof(header), data, std::uint64_t{dwords} * 4u);
buffer->offset += static_cast<std::uint32_t>(bytes);
++buffer->numCommands;
return 0;
}
static std::uint64_t MarkerBytes(const char* text) {
return sizeof(Apr::MarkerCommand) + ((std::strlen(text) + 8) & ~std::uint64_t{7});
}
@ -67,7 +119,7 @@ int APS5_VABI sceAmprCommandBufferWriteAddressOnCompletion(Apr::CommandBufferObj
int APS5_VABI sceAmprCommandBufferWriteCounterOnCompletion(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint32_t value) {
if (!ValidCounter(counter)) return SCE_KERNEL_ERROR_EINVAL;
return AppendCommand(buffer, Apr::Opcode::WriteCounter, Apr::WriteCounterCommand{{}, counter, value});
return AppendCommand(buffer, Apr::Opcode::WriteCounter, Apr::WriteCounterCommand{{}, counter, Apr::CounterAccess::Size4, Apr::CounterOperation::Store, 0, value});
}
int APS5_VABI sceAmprCommandBufferWaitOnAddress(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t reference, std::uint8_t compare, std::uint8_t flush) {
@ -77,7 +129,7 @@ int APS5_VABI sceAmprCommandBufferWaitOnAddress(Apr::CommandBufferObject* buffer
int APS5_VABI sceAmprCommandBufferWaitOnCounter(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint32_t reference, std::uint8_t compare, std::uint8_t flush) {
if (!ValidCounter(counter) || !ValidWait(compare, flush)) return SCE_KERNEL_ERROR_EINVAL;
return AppendCommand(buffer, Apr::Opcode::WaitOnCounter, Apr::WaitCommand{{}, 0, reference, ~0ull, counter, compare});
return AppendCommand(buffer, Apr::Opcode::WaitOnCounter, Apr::WaitCommand{{}, 0, reference, ~0ull, counter, compare, Apr::CounterAccess::Size4, 0});
}
int APS5_VABI sceAmprCommandBufferWriteKernelEventQueueOnCompletion(Apr::CommandBufferObject* buffer, std::uint64_t equeue, std::int32_t ident, std::uint64_t data) {
@ -138,33 +190,35 @@ int APS5_VABI sceAmprAprCommandBufferMapEnd() {
int APS5_VABI sceAmprAprCommandBufferReadFile(Apr::CommandBufferObject* buffer, uint64_t* gatherState, uint64_t* scatterState, uint32_t fileId, void* destination, uint64_t size, uint64_t offset) {
(void)gatherState;
(void)scatterState;
Apr::ReadFileCommand command{};
command.header = {Apr::Opcode::ReadFile, sizeof(command)};
command.fileId = fileId;
command.destination = reinterpret_cast<uint64_t>(destination);
command.size = size;
command.offset = offset;
return Append(buffer, &command, sizeof(command));
if (!ValidRead(destination, size, offset)) return SCE_KERNEL_ERROR_EINVAL;
return AppendRead(buffer, Apr::Opcode::ReadFile, fileId, destination, size, offset);
}
int APS5_VABI sceAmprAprCommandBufferReadFileGather() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprAprCommandBufferReadFileGather(Apr::CommandBufferObject* buffer, std::uint64_t* mapState, std::uint64_t* scatterGatherState, std::uint64_t size, std::uint64_t offset) {
(void)mapState;
(void)scatterGatherState;
if (!ValidReadLength(size) || !ValidReadOffset(offset)) return SCE_KERNEL_ERROR_EINVAL;
return AppendRead(buffer, Apr::Opcode::ReadFileGather, 0, nullptr, size, offset);
}
int APS5_VABI sceAmprAprCommandBufferReadFileGatherScatter() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprAprCommandBufferReadFileGatherScatter(Apr::CommandBufferObject* buffer, std::uint64_t* mapState, std::uint64_t* scatterGatherState, void* destination, std::uint64_t size, std::uint64_t offset) {
(void)mapState;
(void)scatterGatherState;
if (!ValidRead(destination, size, offset)) return SCE_KERNEL_ERROR_EINVAL;
return AppendRead(buffer, Apr::Opcode::ReadFileGatherScatter, 0, destination, size, offset);
}
int APS5_VABI sceAmprAprCommandBufferReadFileScatter() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprAprCommandBufferReadFileScatter(Apr::CommandBufferObject* buffer, std::uint64_t* mapState, std::uint64_t* scatterGatherState, void* destination, std::uint64_t size) {
(void)mapState;
(void)scatterGatherState;
if (!ValidRead(destination, size, 0)) return SCE_KERNEL_ERROR_EINVAL;
return AppendRead(buffer, Apr::Opcode::ReadFileScatter, 0, destination, size, 0);
}
int APS5_VABI sceAmprAprCommandBufferResetGatherScatterState() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprAprCommandBufferResetGatherScatterState(Apr::CommandBufferObject* buffer) {
const int result = AppendCommand(buffer, Apr::Opcode::ResetGatherScatterState, Apr::ResetGatherScatterStateCommand{});
if (result == 0) buffer->recording &= static_cast<std::uint16_t>(~Apr::ScatterGatherValid);
return result;
}
int APS5_VABI sceAmprCommandBufferClearBuffer(Apr::CommandBufferObject* buffer) {
@ -175,14 +229,32 @@ int APS5_VABI sceAmprCommandBufferClearBuffer(Apr::CommandBufferObject* buffer)
return 0;
}
int APS5_VABI sceAmprCommandBufferConstructMarker() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferConstructMarker(Apr::CommandBufferObject* buffer, std::uint32_t type, const char* text, const std::uint32_t* color) {
switch (type) {
case 1: return AppendMarker(buffer, Apr::Opcode::SetMarker, text);
case 2: return AppendMarker(buffer, Apr::Opcode::PushMarker, text);
case 3: return AppendCommand(buffer, Apr::Opcode::PopMarker, Apr::MarkerCommand{});
case 5: return color ? AppendMarker(buffer, Apr::Opcode::SetMarker, text) : SCE_KERNEL_ERROR_EINVAL;
case 6: return color ? AppendMarker(buffer, Apr::Opcode::PushMarker, text) : SCE_KERNEL_ERROR_EINVAL;
default: return SCE_KERNEL_ERROR_EINVAL;
}
}
int APS5_VABI sceAmprCommandBufferConstructNop() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferConstructNop(Apr::CommandBufferObject* buffer, std::int16_t type, const void* payload, std::uint32_t payloadBytes, const std::uint32_t* word) {
(void)type;
if (payloadBytes > (word ? 56u : 60u)) return SCE_KERNEL_ERROR_EINVAL;
if (payloadBytes != 0u && !payload) throw std::invalid_argument("sceAmprCommandBufferConstructNop: null payload");
const std::uint32_t dwords = (payloadBytes + 3u) / 4u + (word ? 1u : 0u);
const std::uint32_t offset = buffer ? buffer->offset : 0u;
const int result = AppendNop(buffer, dwords, nullptr);
if (result != 0) return result;
std::uint8_t* data = buffer->base + offset + sizeof(Apr::CommandHeader);
if (word) {
std::memcpy(data, word, sizeof(*word));
data += sizeof(*word);
}
if (payloadBytes != 0u) std::memcpy(data, payload, payloadBytes);
return 0;
}
int APS5_VABI sceAmprCommandBufferConstructor(Apr::CommandBufferObject* buffer) {
@ -215,14 +287,15 @@ uint32_t APS5_VABI sceAmprCommandBufferGetType(const Apr::CommandBufferObject* b
return static_cast<uint32_t>(buffer->type);
}
int APS5_VABI sceAmprCommandBufferNop() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferNop(Apr::CommandBufferObject* buffer, std::uint32_t dwords) {
if (dwords == 0u || dwords > 16u) return SCE_KERNEL_ERROR_EINVAL;
return AppendNop(buffer, dwords, nullptr);
}
int APS5_VABI sceAmprCommandBufferNopWithData() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferNopWithData(Apr::CommandBufferObject* buffer, std::uint32_t dwords, const std::uint32_t* data) {
if (dwords > 15u) return SCE_KERNEL_ERROR_EINVAL;
if (dwords != 0u && !data) throw std::invalid_argument("sceAmprCommandBufferNopWithData: null data");
return AppendNop(buffer, dwords, data);
}
int APS5_VABI sceAmprCommandBufferPopMarker(Apr::CommandBufferObject* buffer) {
@ -261,29 +334,30 @@ int APS5_VABI sceAmprCommandBufferSetMarkerWithColor(Apr::CommandBufferObject* b
return AppendMarker(buffer, Apr::Opcode::SetMarker, text);
}
int APS5_VABI sceAmprCommandBufferWaitOnAddress_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferWaitOnAddress_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t reference, std::uint8_t compare, std::uint8_t flush) {
if (!ValidWriteAddress(address) || compare > 6u || flush > 1u) return SCE_KERNEL_ERROR_EINVAL;
return AppendCommand(buffer, Apr::Opcode::WaitOnAddress, Apr::WaitCommand{{}, reinterpret_cast<std::uint64_t>(address), reference, ~0ull, 0, compare});
}
int APS5_VABI sceAmprCommandBufferWaitOnCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferWaitOnCounter_04_00(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint8_t access, std::uint64_t reference, std::uint8_t compare, std::uint8_t maskOperation, std::uint64_t mask, std::uint8_t flush) {
if (!ValidWaitOnCounter_04_00(access, compare, maskOperation, flush)) return SCE_KERNEL_ERROR_EINVAL;
const std::uint64_t applied = maskOperation ? mask : ~0ull;
return AppendCommand(buffer, Apr::Opcode::WaitOnCounter, Apr::WaitCommand{{}, 0, reference, applied, counter, compare, static_cast<Apr::CounterAccess>(access), 0});
}
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPair_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPair_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint8_t counter, std::uint64_t atStart) {
(void)atStart;
return sceAmprCommandBufferWriteAddressFromCounterPairOnCompletion(buffer, address, counter);
}
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounter_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint8_t counter, std::uint64_t atStart) {
(void)atStart;
return sceAmprCommandBufferWriteAddressFromCounterOnCompletion(buffer, address, counter);
}
int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounter_04_00(Apr::CommandBufferObject* buffer, volatile std::uint64_t* address, std::uint64_t atStart) {
(void)atStart;
return sceAmprCommandBufferWriteAddressFromTimeCounterOnCompletion(buffer, address);
}
int APS5_VABI sceAmprCommandBufferWriteAddress_04_00(Apr::CommandBufferObject* buffer, uint64_t* address, uint64_t value, uint32_t flags) {
@ -295,14 +369,15 @@ int APS5_VABI sceAmprCommandBufferWriteAddress_04_00(Apr::CommandBufferObject* b
return Append(buffer, &command, sizeof(command));
}
int APS5_VABI sceAmprCommandBufferWriteCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferWriteCounter_04_00(Apr::CommandBufferObject* buffer, std::uint8_t counter, std::uint8_t access, std::uint64_t value, std::uint8_t operation, std::uint8_t atStart) {
(void)atStart;
if (!ValidWriteCounter_04_00(counter, access, operation)) return SCE_KERNEL_ERROR_EINVAL;
return AppendCommand(buffer, Apr::Opcode::WriteCounter, Apr::WriteCounterCommand{{}, counter, static_cast<Apr::CounterAccess>(access), static_cast<Apr::CounterOperation>(operation), 0, value});
}
int APS5_VABI sceAmprCommandBufferWriteKernelEventQueue_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceAmprCommandBufferWriteKernelEventQueue_04_00(Apr::CommandBufferObject* buffer, std::uint64_t equeue, std::int32_t ident, std::uint64_t data, std::uint64_t atStart) {
(void)atStart;
return sceAmprCommandBufferWriteKernelEventQueueOnCompletion(buffer, equeue, ident, data);
}
int APS5_VABI sceAmprMeasureCommandSizeMapBegin() {
@ -320,14 +395,14 @@ int APS5_VABI sceAmprMeasureCommandSizeMapEnd() {
return 0;
}
int APS5_VABI sceAmprMeasureCommandSizeNop() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNop(std::uint32_t dwords) {
if (dwords == 0u || dwords > 16u) return MeasureInvalid;
return NopBytes(dwords);
}
int APS5_VABI sceAmprMeasureCommandSizeNopWithData() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNopWithData(std::uint32_t dwords) {
if (dwords == 0u || dwords > 16u) return MeasureInvalid;
return NopBytes(dwords - 1u);
}
std::uint64_t APS5_VABI sceAmprMeasureCommandSizePopMarker() {
@ -347,24 +422,23 @@ uint32_t APS5_VABI sceAmprMeasureCommandSizeReadFile(void) {
return sizeof(Apr::ReadFileCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeReadFileGather() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGather(std::uint64_t size, std::uint64_t offset) {
if (!ValidReadLength(size) || !ValidReadOffset(offset)) return MeasureInvalid;
return sizeof(Apr::ReadFileCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeReadFileGatherScatter() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGatherScatter(void* destination, std::uint64_t size, std::uint64_t offset) {
if (!ValidRead(destination, size, offset)) return MeasureInvalid;
return sizeof(Apr::ReadFileCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeReadFileScatter() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileScatter(void* destination, std::uint64_t size) {
if (!ValidRead(destination, size, 0)) return MeasureInvalid;
return sizeof(Apr::ReadFileCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeResetGatherScatterState() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeResetGatherScatterState() {
return sizeof(Apr::ResetGatherScatterStateCommand);
}
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarker(const char* text) {
@ -376,43 +450,43 @@ std::uint64_t APS5_VABI sceAmprMeasureCommandSizeSetMarkerWithColor(const char*
return MeasureMarker(text);
}
int APS5_VABI sceAmprMeasureCommandSizeWaitOnAddress_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnAddress_04_00(volatile std::uint64_t* address, std::uint64_t, std::uint8_t compare, std::uint8_t flush) {
if (!ValidWriteAddress(address) || compare > 6u || flush > 1u) return MeasureInvalid;
return sizeof(Apr::WaitCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeWaitOnCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnCounter_04_00(std::uint8_t, std::uint8_t access, std::uint64_t, std::uint8_t compare, std::uint8_t maskOperation, std::uint64_t, std::uint8_t flush) {
if (!ValidWaitOnCounter_04_00(access, compare, maskOperation, flush)) return MeasureInvalid;
return sizeof(Apr::WaitCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00(volatile std::uint64_t* address, std::uint8_t counter) {
if (!ValidWriteAddress(address) || (counter & 1u) != 0u) return MeasureInvalid;
return sizeof(Apr::WriteAddressFromCounterCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounter_04_00(volatile std::uint64_t* address, std::uint8_t counter) {
(void)counter;
if (!ValidWriteAddress(address)) return MeasureInvalid;
return sizeof(Apr::WriteAddressFromCounterCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromTimeCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromTimeCounter_04_00(volatile std::uint64_t* address) {
if (!ValidWriteAddress(address)) return MeasureInvalid;
return sizeof(Apr::WriteAddressFromCounterCommand);
}
uint32_t APS5_VABI sceAmprMeasureCommandSizeWriteAddress_04_00(void) {
return sizeof(Apr::WriteAddressCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeWriteCounter_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteCounter_04_00(std::uint8_t counter, std::uint8_t access, std::uint64_t, std::uint8_t operation) {
if (!ValidWriteCounter_04_00(counter, access, operation)) return MeasureInvalid;
return sizeof(Apr::WriteCounterCommand);
}
int APS5_VABI sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00() {
NotImplemented_nid_no_patch(__func__);
return 0;
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteKernelEventQueue_04_00(std::uint64_t, std::int32_t, std::uint64_t) {
return sizeof(Apr::WriteKernelEventQueueCommand);
}
}

View File

@ -18,4 +18,13 @@ if(BUILD_TESTING)
set_tests_properties(guest_audio3d_parameters PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudio3d>")
endif()
add_test_executable(guest_audio3d_port_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudio3dPort.cpp)
target_include_directories(guest_audio3d_port_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_audio3d_port_tests PRIVATE libSceAudio3d libc)
configure_windows_unwind(guest_audio3d_port_tests)
add_test(NAME guest_audio3d_port COMMAND guest_audio3d_port_tests)
if(WIN32)
set_tests_properties(guest_audio3d_port PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceAudio3d>")
endif()
endif()

View File

@ -1,9 +1,62 @@
#include <chrono>
#include <cstdint>
#include <cstddef>
#include <cstring>
#include <deque>
#include <mutex>
#include <stdexcept>
#include <string>
#include <thread>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr int AUDIO3D_ERROR_INVALID_PORT = static_cast<int>(0x80EA0002);
constexpr int AUDIO3D_ERROR_INVALID_PARAMETER = static_cast<int>(0x80EA0004);
constexpr int AUDIO3D_ERROR_OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
constexpr int AUDIO3D_ERROR_NOT_READY = static_cast<int>(0x80EA0007);
constexpr int AUDIO3D_USER_ID_SYSTEM = 0xFF;
constexpr std::uint32_t AUDIO3D_PORT_ID = 0;
constexpr std::uint32_t AUDIO3D_RATE_48000 = 0;
constexpr std::uint32_t AUDIO3D_SAMPLE_RATE = 48000;
constexpr std::uint32_t AUDIO3D_BUFFER_ADVANCE_AND_PUSH = 2;
constexpr std::uint32_t AUDIO3D_BLOCKING_ASYNC = 0;
constexpr std::uint32_t AUDIO3D_BLOCKING_SYNC = 1;
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL = 0x10001;
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS = 0x10002;
constexpr std::uint32_t AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE = 0x10003;
using Clock = std::chrono::steady_clock;
struct Audio3dPort {
bool open = false;
std::uint32_t queue_depth = 0;
Clock::duration frame_duration{};
std::uint32_t advanced = 0;
std::deque<Clock::time_point> playing;
};
std::mutex g_mutex;
bool g_initialized = false;
Audio3dPort g_port;
[[noreturn]] void Unsupported(const char* function, const std::string& what) {
throw std::runtime_error(std::string(function) + ": " + what);
}
bool PortIsOpen(std::uint32_t port_id) {
return port_id == AUDIO3D_PORT_ID && g_port.open;
}
std::uint32_t QueueLevel(Clock::time_point now) {
while (!g_port.playing.empty() && g_port.playing.front() <= now) g_port.playing.pop_front();
return g_port.advanced + static_cast<std::uint32_t>(g_port.playing.size());
}
}
extern "C" {
void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
@ -14,53 +67,109 @@ void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* p) {
}
int APS5_VABI sceAudio3dInitialize(int64_t reserved) {
(void)reserved;
NotImplemented_nid_no_patch(__func__);
return 0;
if (reserved != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
std::lock_guard lock(g_mutex);
if (g_initialized) Unsupported(__func__, "repeated initialization");
g_initialized = true;
return 0;
}
int APS5_VABI sceAudio3dPortAdvance(uint32_t port_id) {
(void)port_id;
NotImplemented_nid_no_patch(__func__);
return 0;
std::lock_guard lock(g_mutex);
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
if (QueueLevel(Clock::now()) >= g_port.queue_depth) Unsupported(__func__, "queue is full");
++g_port.advanced;
return 0;
}
int APS5_VABI sceAudio3dPortClose(uint32_t port_id) {
(void)port_id;
NotImplemented_nid_no_patch(__func__);
return 0;
std::lock_guard lock(g_mutex);
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
g_port = Audio3dPort{};
return 0;
}
int APS5_VABI sceAudio3dPortGetQueueLevel(uint32_t port_id, uint32_t* queue_level, uint32_t* queue_available) {
(void)port_id;
(void)queue_level;
(void)queue_available;
NotImplemented_nid_no_patch(__func__);
return 0;
std::lock_guard lock(g_mutex);
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
if (queue_level == nullptr && queue_available == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
const std::uint32_t level = QueueLevel(Clock::now());
if (queue_level != nullptr) *queue_level = level;
if (queue_available != nullptr) *queue_available = g_port.queue_depth - level;
return 0;
}
int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, uint32_t* id) {
(void)user_id;
(void)parameters;
(void)id;
NotImplemented_nid_no_patch(__func__);
return 0;
std::lock_guard lock(g_mutex);
if (!g_initialized) return AUDIO3D_ERROR_NOT_READY;
if (user_id != AUDIO3D_USER_ID_SYSTEM || parameters == nullptr || id == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
std::uint32_t num_beds = 2;
switch (parameters->size_this) {
case 0x10:
case 0x18:
Unsupported(__func__, "parameter size " + std::to_string(parameters->size_this) + " selects an unsupported buffer mode");
case 0x20:
break;
case 0x28:
num_beds = parameters->num_beds;
break;
default:
return AUDIO3D_ERROR_INVALID_PARAMETER;
}
if (parameters->rate != AUDIO3D_RATE_48000) return AUDIO3D_ERROR_INVALID_PARAMETER;
if (parameters->granularity < 0x100 || (parameters->granularity & 0xFF) != 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
if (parameters->max_objects == 0 || parameters->queue_depth == 0) return AUDIO3D_ERROR_INVALID_PARAMETER;
if (parameters->buffer_mode > AUDIO3D_BUFFER_ADVANCE_AND_PUSH) return AUDIO3D_ERROR_INVALID_PARAMETER;
if (num_beds != 2 && num_beds != 3) return AUDIO3D_ERROR_INVALID_PARAMETER;
if (parameters->buffer_mode != AUDIO3D_BUFFER_ADVANCE_AND_PUSH) {
Unsupported(__func__, "buffer mode " + std::to_string(parameters->buffer_mode));
}
if (num_beds != 2) Unsupported(__func__, "3 beds");
if (g_port.open) return AUDIO3D_ERROR_OUT_OF_RESOURCES;
g_port = Audio3dPort{};
g_port.open = true;
g_port.queue_depth = parameters->queue_depth;
g_port.frame_duration = std::chrono::duration_cast<Clock::duration>(
std::chrono::nanoseconds(std::uint64_t{parameters->granularity} * 1000000000u / AUDIO3D_SAMPLE_RATE));
*id = AUDIO3D_PORT_ID;
return 0;
}
int APS5_VABI sceAudio3dPortPush(uint32_t port_id, uint32_t blocking) {
(void)port_id;
(void)blocking;
NotImplemented_nid_no_patch(__func__);
return 0;
Clock::time_point wait_until;
{
std::lock_guard lock(g_mutex);
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
if (blocking != AUDIO3D_BLOCKING_ASYNC && blocking != AUDIO3D_BLOCKING_SYNC) {
Unsupported(__func__, "blocking mode " + std::to_string(blocking));
}
const Clock::time_point now = Clock::now();
const std::uint32_t level = QueueLevel(now);
Clock::time_point end = g_port.playing.empty() ? now : g_port.playing.back();
for (; g_port.advanced != 0; --g_port.advanced) {
end += g_port.frame_duration;
g_port.playing.push_back(end);
}
if (blocking == AUDIO3D_BLOCKING_ASYNC || level < g_port.queue_depth) return 0;
wait_until = g_port.playing[level - g_port.queue_depth];
}
while (Clock::now() < wait_until) std::this_thread::sleep_until(wait_until);
return 0;
}
int APS5_VABI sceAudio3dPortSetAttribute(uint32_t port_id, uint32_t attribute_id, const void* attribute, size_t attribute_size) {
(void)port_id;
(void)attribute_id;
(void)attribute;
(void)attribute_size;
NotImplemented_nid_no_patch(__func__);
return 0;
std::lock_guard lock(g_mutex);
if (!PortIsOpen(port_id)) return AUDIO3D_ERROR_INVALID_PORT;
if (attribute == nullptr) return AUDIO3D_ERROR_INVALID_PARAMETER;
if (attribute_size != 4) Unsupported(__func__, "attribute size " + std::to_string(attribute_size));
switch (attribute_id) {
case AUDIO3D_ATTRIBUTE_LATE_REVERB_LEVEL:
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_RADIUS:
case AUDIO3D_ATTRIBUTE_DOWNMIX_SPREAD_HEIGHT_AWARE:
return 0;
default:
Unsupported(__func__, "attribute " + std::to_string(attribute_id));
}
}
}

View File

@ -26,6 +26,11 @@ int APS5_VABI sceContentExportFinish(void) {
return 0;
}
int APS5_VABI sceContentExportFromData(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceContentExportFromFile(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@ -47,10 +52,4 @@ int APS5_VABI sceContentExportTerm(void) {
return 0;
}
APS5_EXPORT("AOWqIYsgVHs", sceContentExportUnknown_AOWqIYsgVHs);
int32_t APS5_VABI sceContentExportUnknown_AOWqIYsgVHs(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -12,8 +12,13 @@ if(BUILD_TESTING)
target_include_directories(guest_font_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_font_tests PRIVATE libSceFont libSceFontFt libc)
add_test(NAME guest_font COMMAND guest_font_tests)
add_test_executable(guest_system_font_tests ${LIBS_INCLUDE_DIR}/tests/GuestSystemFont.cpp)
target_include_directories(guest_system_font_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_system_font_tests PRIVATE libSceFont libSceFontFt libc)
configure_windows_unwind(guest_system_font_tests)
add_test(NAME guest_system_font COMMAND guest_system_font_tests)
if(WIN32)
set_tests_properties(guest_font PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(guest_font guest_system_font PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceFont>")
endif()
endif()

View File

@ -7,6 +7,7 @@ function(add_sce_font_library target)
${fontDir}/src/Render.cpp
${fontDir}/src/State.cpp
${fontDir}/src/Style.cpp
${fontDir}/src/SystemFont.cpp
${fontDir}/src/Text.cpp
${fontDir}/src/Unimplemented.cpp
)

View File

@ -23,6 +23,7 @@ constexpr std::uint16_t STYLE_FRAME_MAGIC = 0x0F09;
constexpr std::uint32_t LOCK_BIT = 0x80000000u;
constexpr std::uint32_t OPEN_BIT = 0x40000000u;
constexpr std::uint32_t COUNT_MASK = 0x0FFFFFFFu;
constexpr std::uint64_t SYSFONT_FLAG_SYSTEM_SET = 0x100u;
constexpr std::size_t HORIZONTAL_LAYOUT_SIZE = 0x30;
constexpr std::size_t HORIZONTAL_LINE_ADVANCE = 0x00;

View File

@ -6,6 +6,9 @@
#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <memory>
#include <optional>
#include <vector>
#include <ft2build.h>
@ -21,7 +24,7 @@ constexpr std::uint8_t STYLE_FRAME_FLAG_WEIGHT = 0x04;
constexpr float POINTS_PER_INCH = 72.0f;
struct FontState {
std::vector<unsigned char> faceData;
std::shared_ptr<const std::vector<unsigned char>> faceData;
FT_Face face = nullptr;
float scaleW = 16.0f;
float scaleH = 16.0f;
@ -48,6 +51,11 @@ struct GeneratedGlyph {
bool outlineInitialized = false;
};
struct SystemFontFile {
std::filesystem::path path;
std::uint32_t subFontIndex = 0;
};
struct RenderSurfaceSystemUse {
FontStyleFrame* styleframe;
float catchedScale;
@ -70,12 +78,18 @@ void** EntryObjectSlot(FontCtxEntry* entry, std::uint32_t modeLow);
FontCtxEntry* AcquireFontCtxEntry(void* ctx, std::uint32_t index, std::uint32_t modeLow, FontObj** outObject, std::uint32_t* outLockWord);
void ReleaseFontCtxEntryLock(FontCtxEntry* entry, std::uint32_t modeLow, std::uint32_t lockWord);
FontObj* FindSubFont(FontObj* head, std::uint32_t subFontIndex);
void* FontContext(const FontLibNative* library, const FontHandleNative* font);
void ReleaseFontObjectsForHandle(FontHandleNative* font);
FontState* TryGetState(FontHandle handle);
FontState& ResetState(FontHandle handle);
void RemoveState(FontHandle handle);
void LoadStateFace(FontState& state, std::vector<unsigned char> bytes, std::uint32_t subFontIndex);
void LoadStateFace(FontState& state, std::shared_ptr<const std::vector<unsigned char>> bytes, std::uint32_t subFontIndex);
bool IsSystemFontSet(std::uint32_t fontSetType);
std::vector<SystemFontFile> SystemFontCandidates(std::uint32_t fontSetType);
std::filesystem::path SystemFontDirectory();
std::optional<SystemFontFile> FindSystemFontFile(std::uint32_t fontSetType);
void TrackGeneratedGlyph(FontGlyph glyph);
bool ForgetGeneratedGlyph(FontGlyph glyph);

View File

@ -212,7 +212,7 @@ int Font::ComputeHorizontalLayout(FontHandle handle, const StyleStateBlock* styl
auto* font = GetNativeFont(handle);
if (!font) return fail();
auto* library = static_cast<FontLibNative*>(font->library);
if (!library || !library->external_fonts_ctx) return fail();
if (!library || !FontContext(library, font)) return fail();
const SysDriver* driver = library->sys_driver;
if (!driver || !driver->set_char_with_dpi || !driver->set_char_default_dpi || !driver->compute_layout || !style) return fail();
const std::uint32_t modeLow = font->flags & 0x0Fu;
@ -226,7 +226,7 @@ int Font::ComputeHorizontalLayout(FontHandle handle, const StyleStateBlock* styl
if (static_cast<std::int32_t>(fontId) >= 0) {
FontObj* head = nullptr;
std::uint32_t lockWord = 0;
auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, fontId, modeLow, &head, &lockWord);
auto* entry = AcquireFontCtxEntry(FontContext(library, font), fontId, modeLow, &head, &lockWord);
if (!entry) return fail();
FontObj* match = FindSubFont(head, font->open_info.sub_font_index);
int rc = SCE_FONT_ERROR_FATAL;
@ -288,7 +288,7 @@ int Font::ComputeVerticalLayout(FontHandle handle, const StyleStateBlock* style,
};
auto* font = GetNativeFont(handle);
auto* library = font ? static_cast<FontLibNative*>(font->library) : nullptr;
if (!library || !library->external_fonts_ctx || !library->sys_driver) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE);
if (!library || !FontContext(library, font) || !library->sys_driver) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE);
const SysDriver* driver = library->sys_driver;
const std::uint32_t modeLow = font->flags & 0x0Fu;
const std::uint32_t fontId = font->open_info.ctx_entry_index;
@ -303,7 +303,7 @@ int Font::ComputeVerticalLayout(FontHandle handle, const StyleStateBlock* style,
if (static_cast<std::int32_t>(fontId) >= 0) {
FontObj* head = nullptr;
std::uint32_t lockWord = 0;
auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, fontId, modeLow, &head, &lockWord);
auto* entry = AcquireFontCtxEntry(FontContext(library, font), fontId, modeLow, &head, &lockWord);
FontObj* obj = FindSubFont(head, font->open_info.sub_font_index);
if (!entry || !obj) {
if (entry) ReleaseFontCtxEntryLock(entry, modeLow, lockWord);
@ -454,7 +454,7 @@ int Font::RenderCharGlyphImageCore(FontHandle handle, std::uint32_t code, FontRe
const StyleStateBlock* style = ResolveSurfaceStyleState(&font->cached_style.state, surface, surfaceStyle);
FontObj* obj = nullptr;
std::uint32_t lockWord = 0;
auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, font->open_info.ctx_entry_index, modeLow, &obj, &lockWord);
auto* entry = AcquireFontCtxEntry(FontContext(library, font), font->open_info.ctx_entry_index, modeLow, &obj, &lockWord);
obj = FindSubFont(obj, font->open_info.sub_font_index);
if (!entry || !obj) {
if (entry) ReleaseFontCtxEntryLock(entry, modeLow, lockWord);

View File

@ -1,13 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
@ -18,28 +20,11 @@ namespace {
using namespace Font;
constexpr std::array<std::uint32_t, 88> SYSTEM_FONT_SET_TYPES = {
0x18070043u, 0x18070044u, 0x18070045u, 0x18070047u, 0x18070053u, 0x18070054u,
0x18070055u, 0x18070057u, 0x180700C3u, 0x180700C4u, 0x180700C5u, 0x180700C7u,
0x18070444u, 0x18070447u, 0x18070454u, 0x18070457u, 0x180704C4u, 0x180704C7u,
0x18071053u, 0x18071054u, 0x18071055u, 0x18071057u, 0x18071454u, 0x18071457u,
0x18072444u, 0x18072447u, 0x180724C4u, 0x180724C7u, 0x18073454u, 0x18073457u,
0x180734D4u, 0x180734D7u, 0x18078044u, 0x180780C4u, 0x18079054u, 0x1807A044u,
0x1807A0C4u, 0x1807A444u, 0x1807A4C4u, 0x1807AC44u, 0x1807ACC4u, 0x1807B054u,
0x1807B0D4u, 0x1807B454u, 0x1807B4D4u, 0x1807BC54u, 0x1807BCD4u, 0x18080444u,
0x18080447u, 0x18080454u, 0x18080457u, 0x180804C4u, 0x180804C7u, 0x18081454u,
0x18081457u, 0x18082444u, 0x18082447u, 0x180824C4u, 0x180824C7u, 0x18083454u,
0x18083457u, 0x180834D4u, 0x180834D7u, 0x1808A444u, 0x1808A4C4u, 0x1808B454u,
0x1808B4D4u, 0x180C8044u, 0x180C80C4u, 0x180C9054u, 0x180CA044u, 0x180CA0C4u,
0x180CAC44u, 0x180CACC4u, 0x180CB054u, 0x180CB0D4u, 0x180CBC54u, 0x180CBCD4u,
0x18170043u, 0x18170044u, 0x18170045u, 0x18170047u, 0x18170444u, 0x18170447u,
0x18370044u, 0x18370047u, 0x18370444u, 0x18370447u,
};
constexpr std::uint16_t HANDLE_OWNED = 0x10;
struct OpenRequest {
FontLibNative* library;
void* context;
std::uint32_t modeLow;
std::uint32_t driverMode;
const void* source;
@ -111,7 +96,7 @@ void SetOpenFlags(FontLibNative* library, std::uint32_t modeLow) {
if ((flags & secondBit) != 0) library->sysfont_flags |= 2;
}
std::uint32_t ClaimExternalEntry(FontCtxHeader* header, const OpenRequest& request) {
std::uint32_t ClaimEntry(FontCtxHeader* header, const OpenRequest& request) {
AcquireWordLock(header->lock_word);
auto* entries = static_cast<FontCtxEntry*>(header->base);
std::uint32_t entryIndex = 0xFFFFFFFFu;
@ -140,7 +125,7 @@ std::uint32_t ClaimExternalEntry(FontCtxHeader* header, const OpenRequest& reque
return entryIndex;
}
int OpenExternalFont(const OpenRequest& request, std::uint32_t libraryLock, FontHandle* pFontHandle) {
int OpenFontInContext(const OpenRequest& request, std::uint32_t libraryLock, FontHandle* pFontHandle) {
FontLibNative* library = request.library;
FontHandle handle = PrepareHandle(library, *pFontHandle);
if (!handle) {
@ -154,8 +139,8 @@ int OpenExternalFont(const OpenRequest& request, std::uint32_t libraryLock, Font
*pFontHandle = nullptr;
return rc;
};
auto* header = static_cast<FontCtxHeader*>(library->external_fonts_ctx);
const std::uint32_t entryIndex = ClaimExternalEntry(header, request);
auto* header = static_cast<FontCtxHeader*>(request.context);
const std::uint32_t entryIndex = ClaimEntry(header, request);
if (entryIndex == 0xFFFFFFFFu) return fail(SCE_FONT_ERROR_FONT_OPEN_MAX);
const std::uint32_t packedId = request.uniqueId == -1 ? entryIndex + 0x40000000u : (static_cast<std::uint32_t>(request.uniqueId) | 0x80000000u);
FontObj* fontObj = nullptr;
@ -183,6 +168,7 @@ int OpenExternalFont(const OpenRequest& request, std::uint32_t libraryLock, Font
}
if (needOpen) {
SetOpenFlags(library, request.modeLow);
if (request.context == library->sysfonts_ctx) library->sysfont_flags |= SYSFONT_FLAG_SYSTEM_SET;
rc = driver->open(library, request.driverMode, request.source, request.sourceSize, request.subFontIndex, packedId, &fontObj);
library->sysfont_flags = 0;
if (rc == SCE_FONT_OK) {
@ -221,11 +207,23 @@ int BeginOpen(FontLibrary library, FontHandle* pFontHandle, std::uint32_t& libra
return rc;
}
bool ReadFileBytes(const std::filesystem::path& path, std::vector<unsigned char>& bytes) {
std::shared_ptr<const std::vector<unsigned char>> ReadFileBytes(const std::filesystem::path& path) {
std::ifstream file(path, std::ios::binary);
if (!file) return false;
bytes.assign(std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>());
return !file.bad();
if (!file) return nullptr;
std::vector<unsigned char> bytes{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
if (file.bad()) return nullptr;
return std::make_shared<const std::vector<unsigned char>>(std::move(bytes));
}
std::shared_ptr<const std::vector<unsigned char>> SystemFontBytes(const std::filesystem::path& path) {
static std::mutex mutex;
static std::map<std::filesystem::path, std::weak_ptr<const std::vector<unsigned char>>> loaded;
std::lock_guard lock(mutex);
auto& slot = loaded[path];
if (auto bytes = slot.lock()) return bytes;
auto bytes = ReadFileBytes(path);
slot = bytes;
return bytes;
}
void CopyStyleToCache(FontHandleNative* font) {
@ -250,11 +248,11 @@ int APS5_VABI sceFontOpenFontMemory(FontLibrary library, const void* fontAddress
if (pFontHandle) *pFontHandle = nullptr;
return SCE_FONT_ERROR_INVALID_PARAMETER;
}
const OpenRequest request{lib, 1, 1, fontAddress, fontSize, true, subFontIndex, uniqueId};
const int rc = OpenExternalFont(request, libraryLock, pFontHandle);
const OpenRequest request{lib, lib->external_fonts_ctx, 1, 1, fontAddress, fontSize, true, subFontIndex, uniqueId};
const int rc = OpenFontInContext(request, libraryLock, pFontHandle);
if (rc != SCE_FONT_OK) return rc;
const auto* bytes = static_cast<const unsigned char*>(fontAddress);
LoadStateFace(ResetState(*pFontHandle), std::vector<unsigned char>(bytes, bytes + fontSize), subFontIndex);
LoadStateFace(ResetState(*pFontHandle), std::make_shared<const std::vector<unsigned char>>(bytes, bytes + fontSize), subFontIndex);
return SCE_FONT_OK;
}
@ -278,12 +276,11 @@ int APS5_VABI sceFontOpenFontFile(FontLibrary library, const char* path, std::ui
*pFontHandle = nullptr;
return SCE_FONT_ERROR_INVALID_PARAMETER;
}
const OpenRequest request{lib, modeLow, modeLow + 4, hostPathString.c_str(), 0, false, subFontIndex, uniqueId};
const int rc = OpenExternalFont(request, libraryLock, pFontHandle);
const OpenRequest request{lib, lib->external_fonts_ctx, modeLow, modeLow + 4, hostPathString.c_str(), 0, false, subFontIndex, uniqueId};
const int rc = OpenFontInContext(request, libraryLock, pFontHandle);
if (rc != SCE_FONT_OK) return rc;
std::vector<unsigned char> bytes;
FontState& state = ResetState(*pFontHandle);
if (ReadFileBytes(hostPath, bytes)) LoadStateFace(state, std::move(bytes), subFontIndex);
if (auto bytes = ReadFileBytes(hostPath)) LoadStateFace(state, std::move(bytes), subFontIndex);
return SCE_FONT_OK;
}
@ -309,7 +306,7 @@ int APS5_VABI sceFontOpenFontInstance(FontHandle fontHandle, FontHandle setupFon
const std::uint32_t modeLow = source->flags & 0x0Fu;
const std::uint32_t subFontIndex = source->open_info.sub_font_index;
const std::uint32_t entryIndex = source->open_info.ctx_entry_index;
auto* header = static_cast<FontCtxHeader*>(library->external_fonts_ctx);
auto* header = static_cast<FontCtxHeader*>(FontContext(library, source));
if (!header) return fail(SCE_FONT_ERROR_INVALID_FONT_HANDLE);
FontHandle target = setupFont;
bool owned = false;
@ -371,7 +368,7 @@ int APS5_VABI sceFontOpenFontInstance(FontHandle fontHandle, FontHandle setupFon
if (FontState* sourceState = TryGetState(fontHandle)) {
targetState.scaleW = sourceState->scaleW;
targetState.scaleH = sourceState->scaleH;
if (!sourceState->faceData.empty()) LoadStateFace(targetState, sourceState->faceData, subFontIndex);
if (sourceState->faceData && !sourceState->faceData->empty()) LoadStateFace(targetState, sourceState->faceData, subFontIndex);
}
if (pFontHandle) *pFontHandle = target;
return SCE_FONT_OK;
@ -385,6 +382,8 @@ int APS5_VABI sceFontOpenFontSet(FontLibrary library, std::uint32_t fontSetType,
return SCE_FONT_ERROR_INVALID_LIBRARY;
}
if (!pFontHandle) return SCE_FONT_ERROR_INVALID_PARAMETER;
const std::optional<SystemFontFile> file = FindSystemFontFile(fontSetType);
APS5_LOG_OUT("fontSetType=0x%08X file=%s", fontSetType, file ? file->path.string().c_str() : "(none)");
std::uint32_t libraryLock = 0;
if (!AcquireLibraryLock(lib, libraryLock)) {
*pFontHandle = nullptr;
@ -399,10 +398,20 @@ int APS5_VABI sceFontOpenFontSet(FontLibrary library, std::uint32_t fontSetType,
rc = SCE_FONT_ERROR_INVALID_MEMORY;
} else if (!lib->sysfonts_ctx) {
rc = SCE_FONT_ERROR_NO_SUPPORT_FUNCTION;
} else if (std::find(SYSTEM_FONT_SET_TYPES.begin(), SYSTEM_FONT_SET_TYPES.end(), fontSetType) == SYSTEM_FONT_SET_TYPES.end()) {
} else if (!IsSystemFontSet(fontSetType)) {
rc = SCE_FONT_ERROR_NO_SUPPORT_FONTSET;
} else {
} else if (!file) {
rc = SCE_FONT_ERROR_FONT_OPEN_FAILED;
} else {
const std::string hostPathString = file->path.string();
const OpenRequest request{lib, lib->sysfonts_ctx, openMode, openMode + 4, hostPathString.c_str(), 0, false, file->subFontIndex, static_cast<std::int32_t>(fontSetType)};
const int openRc = OpenFontInContext(request, libraryLock, pFontHandle);
if (openRc == SCE_FONT_ERROR_FS_OPEN_FAILED || openRc == SCE_FONT_ERROR_NO_SUPPORT_FORMAT) return SCE_FONT_ERROR_FONT_OPEN_FAILED;
if (openRc != SCE_FONT_OK) return openRc;
GetNativeFont(*pFontHandle)->open_info.fontset_flags = fontSetType;
FontState& state = ResetState(*pFontHandle);
if (auto bytes = SystemFontBytes(file->path)) LoadStateFace(state, std::move(bytes), file->subFontIndex);
return SCE_FONT_OK;
}
ReleaseLibraryLock(lib, libraryLock);
*pFontHandle = nullptr;

View File

@ -193,6 +193,10 @@ Font::FontObj* Font::FindSubFont(FontObj* head, std::uint32_t subFontIndex) {
return nullptr;
}
void* Font::FontContext(const FontLibNative* library, const FontHandleNative* font) {
return font->open_info.fontset_flags != 0 ? library->sysfonts_ctx : library->external_fonts_ctx;
}
void Font::ReleaseFontObjectsForHandle(FontHandleNative* font) {
auto* library = static_cast<FontLibNative*>(font->library);
if (!library || library->magic != LIBRARY_MAGIC || !library->sys_driver || !library->sys_driver->close) return;
@ -201,7 +205,7 @@ void Font::ReleaseFontObjectsForHandle(FontHandleNative* font) {
const std::uint32_t modeLow = font->flags & 0x0Fu;
FontObj* head = nullptr;
std::uint32_t lockWord = 0;
auto* entry = AcquireFontCtxEntry(library->external_fonts_ctx, fontId, modeLow, &head, &lockWord);
auto* entry = AcquireFontCtxEntry(FontContext(library, font), fontId, modeLow, &head, &lockWord);
if (!entry) return;
FontObj* match = FindSubFont(head, font->open_info.sub_font_index);
const std::uint32_t openFlag = lockWord & OPEN_BIT;
@ -243,16 +247,16 @@ void Font::RemoveState(FontHandle handle) {
fontStates.erase(handle);
}
void Font::LoadStateFace(FontState& state, std::vector<unsigned char> bytes, std::uint32_t subFontIndex) {
void Font::LoadStateFace(FontState& state, std::shared_ptr<const std::vector<unsigned char>> bytes, std::uint32_t subFontIndex) {
if (state.face) {
FT_Done_Face(state.face);
state.face = nullptr;
}
state.faceData = std::move(bytes);
const FT_Library library = HostFreeTypeLibrary();
if (!library || state.faceData.empty()) return;
if (!library || !state.faceData || state.faceData->empty()) return;
FT_Face face = nullptr;
if (FT_New_Memory_Face(library, state.faceData.data(), static_cast<FT_Long>(state.faceData.size()), static_cast<FT_Long>(subFontIndex), &face) != 0) return;
if (FT_New_Memory_Face(library, state.faceData->data(), static_cast<FT_Long>(state.faceData->size()), static_cast<FT_Long>(subFontIndex), &face) != 0) return;
FT_Select_Charmap(face, FT_ENCODING_UNICODE);
state.face = face;
}

View File

@ -0,0 +1,131 @@
#include <algorithm>
#include <array>
#include <cstdlib>
#include <stdexcept>
#include <string_view>
#include <system_error>
#ifdef _WIN32
#include <windows.h>
#endif
#include "prx/libSceFont/include/FontInternal.hpp"
namespace {
constexpr std::array<std::uint32_t, 120> SYSTEM_FONT_SET_TYPES = {
0x18070043u, 0x18070044u, 0x18070045u, 0x18070047u, 0x18070053u, 0x18070054u,
0x18070055u, 0x18070057u, 0x180700C3u, 0x180700C4u, 0x180700C5u, 0x180700C7u,
0x18070444u, 0x18070447u, 0x18070454u, 0x18070457u, 0x180704C4u, 0x180704C7u,
0x18071053u, 0x18071054u, 0x18071055u, 0x18071057u, 0x18071454u, 0x18071457u,
0x18072444u, 0x18072447u, 0x180724C4u, 0x180724C7u, 0x18073454u, 0x18073457u,
0x180734D4u, 0x180734D7u, 0x18078044u, 0x180780C4u, 0x18079054u, 0x1807A044u,
0x1807A0C4u, 0x1807A444u, 0x1807A4C4u, 0x1807AC44u, 0x1807ACC4u, 0x1807B054u,
0x1807B0D4u, 0x1807B454u, 0x1807B4D4u, 0x1807BC54u, 0x1807BCD4u, 0x18080444u,
0x18080447u, 0x18080454u, 0x18080457u, 0x180804C4u, 0x180804C7u, 0x18081454u,
0x18081457u, 0x18082444u, 0x18082447u, 0x180824C4u, 0x180824C7u, 0x18083454u,
0x18083457u, 0x180834D4u, 0x180834D7u, 0x1808A444u, 0x1808A4C4u, 0x1808B454u,
0x1808B4D4u, 0x180C8044u, 0x180C80C4u, 0x180C9054u, 0x180CA044u, 0x180CA0C4u,
0x180CAC44u, 0x180CACC4u, 0x180CB054u, 0x180CB0D4u, 0x180CBC54u, 0x180CBCD4u,
0x18170043u, 0x18170044u, 0x18170045u, 0x18170047u, 0x18170444u, 0x18170447u,
0x18370044u, 0x18370047u, 0x18370444u, 0x18370447u, 0x1A070543u, 0x1A070547u,
0x1A070553u, 0x1A070557u, 0x1A0705C3u, 0x1A0705C7u, 0x1A071553u, 0x1A071557u,
0x1A072543u, 0x1A072547u, 0x1A0725C3u, 0x1A0725C7u, 0x1A073553u, 0x1A073557u,
0x1A0735D3u, 0x1A0735D7u, 0x1A080543u, 0x1A080547u, 0x1A080553u, 0x1A080557u,
0x1A0805C3u, 0x1A0805C7u, 0x1A081553u, 0x1A081557u, 0x1A082543u, 0x1A082547u,
0x1A0825C3u, 0x1A0825C7u, 0x1A083553u, 0x1A083557u, 0x1A0835D3u, 0x1A0835D7u,
};
enum class Family { Latin, LatinItalic, Typewriter, Vietnamese, Thai, Japanese, ChineseGb };
struct FamilyFiles {
std::array<std::string_view, 4> system;
std::array<std::string_view, 4> substitute;
std::uint32_t substituteSubFont;
};
constexpr std::array<FamilyFiles, 7> FAMILY_FILES = {{
{{"SST-Light.otf", "SST-Roman.otf", "SST-Medium.otf", "SST-Bold.otf"},
{"NotoSans-Light.ttf", "NotoSans-Regular.ttf", "NotoSans-Medium.ttf", "NotoSans-Bold.ttf"}, 0},
{{"SST-LightItalic.otf", "SST-Italic.otf", "SST-MediumItalic.otf", "SST-BoldItalic.otf"},
{"NotoSans-LightItalic.ttf", "NotoSans-Italic.ttf", "NotoSans-MediumItalic.ttf", "NotoSans-BoldItalic.ttf"}, 0},
{{"SSTTypewriter-Roman.otf", "SSTTypewriter-Roman.otf", "SSTTypewriter-Roman.otf", "SSTTypewriter-Bd.otf"},
{"NotoSansMono-Light.ttf", "NotoSansMono-Regular.ttf", "NotoSansMono-Medium.ttf", "NotoSansMono-Bold.ttf"}, 0},
{{"SSTVietnamese-Light.otf", "SSTVietnamese-Roman.otf", "SSTVietnamese-Medium.otf", "SSTVietnamese-Bold.otf"},
{"NotoSans-Light.ttf", "NotoSans-Regular.ttf", "NotoSans-Medium.ttf", "NotoSans-Bold.ttf"}, 0},
{{"SSTThai-Light.otf", "SSTThai-Roman.otf", "SSTThai-Medium.otf", "SSTThai-Bold.otf"},
{"NotoSansThai-Light.ttf", "NotoSansThai-Regular.ttf", "NotoSansThai-Medium.ttf", "NotoSansThai-Bold.ttf"}, 0},
{{"SSTJpPro-Regular.otf", "SSTJpPro-Regular.otf", "SSTJpPro-Regular.otf", "SSTJpPro-Bold.otf"},
{"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 0},
{{"DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf", "DFHEI5-SONY.ttf"},
{"NotoSansCJK-Light.ttc", "NotoSansCJK-Regular.ttc", "NotoSansCJK-Medium.ttc", "NotoSansCJK-Bold.ttc"}, 2},
}};
Family FamilyOf(std::uint32_t fontSetType) {
const std::uint32_t region = (fontSetType >> 16) & 0xFFu;
const std::uint32_t scripts = (fontSetType >> 8) & 0xFEu;
const bool vietnamese = (fontSetType & 0x10u) != 0;
switch (region) {
case 0x17: return Family::LatinItalic;
case 0x37: return Family::Typewriter;
case 0x08: return scripts == 0x04 && vietnamese ? Family::Vietnamese : Family::Japanese;
case 0x0C: return Family::ChineseGb;
default:
if (scripts == 0x10) return Family::Thai;
if (scripts == 0 && vietnamese) return Family::Vietnamese;
return Family::Latin;
}
}
std::size_t WeightOf(std::uint32_t fontSetType) {
switch (fontSetType & 0x0Fu) {
case 0x3: return 0;
case 0x5: return 2;
case 0x7: return 3;
default: return 1;
}
}
std::filesystem::path ExecutableDirectory() {
#ifdef _WIN32
wchar_t module[MAX_PATH];
const auto length = GetModuleFileNameW(nullptr, module, MAX_PATH);
if (length == 0 || length == MAX_PATH) throw std::runtime_error("libSceFont: cannot locate the executable");
return std::filesystem::path(module).parent_path();
#else
return std::filesystem::read_symlink("/proc/self/exe").parent_path();
#endif
}
}
bool Font::IsSystemFontSet(std::uint32_t fontSetType) {
return std::find(SYSTEM_FONT_SET_TYPES.begin(), SYSTEM_FONT_SET_TYPES.end(), fontSetType) != SYSTEM_FONT_SET_TYPES.end();
}
std::vector<Font::SystemFontFile> Font::SystemFontCandidates(std::uint32_t fontSetType) {
if (!IsSystemFontSet(fontSetType)) return {};
const FamilyFiles& files = FAMILY_FILES[static_cast<std::size_t>(FamilyOf(fontSetType))];
const std::size_t weight = WeightOf(fontSetType);
return {{std::filesystem::path(files.system[weight]), 0}, {std::filesystem::path(files.substitute[weight]), files.substituteSubFont}};
}
std::filesystem::path Font::SystemFontDirectory() {
const char* configured = std::getenv("ANYPS5_SYSTEM_FONTS");
if (!configured || configured[0] == '\0') return ExecutableDirectory() / "anyps5-fonts";
const std::filesystem::path directory(configured);
std::error_code error;
if (!std::filesystem::is_directory(directory, error)) throw std::runtime_error("libSceFont: ANYPS5_SYSTEM_FONTS is not a directory: " + directory.string());
return directory;
}
std::optional<Font::SystemFontFile> Font::FindSystemFontFile(std::uint32_t fontSetType) {
const std::vector<SystemFontFile> candidates = SystemFontCandidates(fontSetType);
if (candidates.empty()) return std::nullopt;
const std::filesystem::path directory = SystemFontDirectory();
for (const SystemFontFile& candidate : candidates) {
std::error_code error;
const std::filesystem::path path = directory / candidate.path;
if (std::filesystem::is_regular_file(path, error)) return SystemFontFile{path, candidate.subFontIndex};
}
return std::nullopt;
}

View File

@ -457,6 +457,16 @@ int APS5_VABI sceFontWritingLineWritesOrder() {
return 0;
}
int APS5_VABI sceFontGraphicsDrawupFillTextureImageObject(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceFontGraphicsDrawupFillTexturePatternObject(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}
#pragma GCC visibility pop

View File

@ -5,6 +5,7 @@
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <limits>
@ -26,6 +27,8 @@ struct FtLibraryContext {
const FontMemoryInterface* iface;
FT_Memory memory;
FT_Library library;
FT_Memory hostMemory;
FT_Library hostLibrary;
};
void* FtAlloc(FT_Memory memory, long size) {
@ -58,6 +61,42 @@ void* FtRealloc(FT_Memory memory, long currentSize, long newSize, void* block) {
return result;
}
void* HostAlloc(FT_Memory, long size) {
return size > 0 ? std::malloc(static_cast<std::size_t>(size)) : nullptr;
}
void HostFree(FT_Memory, void* block) {
std::free(block);
}
void* HostRealloc(FT_Memory, long, long newSize, void* block) {
if (newSize <= 0) {
std::free(block);
return nullptr;
}
return std::realloc(block, static_cast<std::size_t>(newSize));
}
FT_Library SystemFontLibrary(FtLibraryContext* ctx) {
if (ctx->hostLibrary) return ctx->hostLibrary;
auto* hostMemory = static_cast<FT_Memory>(ctx->iface->alloc(ctx->allocCtx, sizeof(FT_MemoryRec_)));
if (!hostMemory) return nullptr;
std::memset(hostMemory, 0, sizeof(*hostMemory));
hostMemory->user = ctx;
hostMemory->alloc = &HostAlloc;
hostMemory->free = &HostFree;
hostMemory->realloc = &HostRealloc;
FT_Library hostLibrary = nullptr;
if (FT_New_Library(hostMemory, &hostLibrary) != 0 || !hostLibrary) {
ctx->iface->dealloc(ctx->allocCtx, hostMemory);
return nullptr;
}
FT_Add_Default_Modules(hostLibrary);
ctx->hostMemory = hostMemory;
ctx->hostLibrary = hostLibrary;
return hostLibrary;
}
void WriteU16(std::uint8_t* out, std::size_t offset, std::uint16_t value) {
std::memcpy(out + offset, &value, sizeof(value));
}
@ -131,6 +170,14 @@ int APS5_VABI LibraryTerm(FontLibNative* library) {
FT_Done_Library(ctx->library);
ctx->library = nullptr;
}
if (ctx->hostLibrary) {
FT_Done_Library(ctx->hostLibrary);
ctx->hostLibrary = nullptr;
}
if (ctx->hostMemory) {
freeFn(library->alloc_ctx, ctx->hostMemory);
ctx->hostMemory = nullptr;
}
if (ctx->memory) {
freeFn(library->alloc_ctx, ctx->memory);
ctx->memory = nullptr;
@ -162,12 +209,15 @@ int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const
}
auto* ctx = static_cast<FtLibraryContext*>(library->fontset_registry);
if (!ctx || !ctx->library) return SCE_FONT_ERROR_INVALID_LIBRARY;
const bool systemSet = (library->sysfont_flags & SYSFONT_FLAG_SYSTEM_SET) != 0;
FT_Library* ftLibrary = systemSet ? &ctx->hostLibrary : &ctx->library;
if (systemSet && !SystemFontLibrary(ctx)) return SCE_FONT_ERROR_ALLOCATION_FAILED;
FT_Face face = nullptr;
FT_Error error;
if (fromMemory) {
error = FT_New_Memory_Face(ctx->library, static_cast<const FT_Byte*>(fontAddress), static_cast<FT_Long>(fontSize), static_cast<FT_Long>(subFontIndex), &face);
error = FT_New_Memory_Face(*ftLibrary, static_cast<const FT_Byte*>(fontAddress), static_cast<FT_Long>(fontSize), static_cast<FT_Long>(subFontIndex), &face);
} else {
error = FT_New_Face(ctx->library, static_cast<const char*>(fontAddress), static_cast<FT_Long>(subFontIndex), &face);
error = FT_New_Face(*ftLibrary, static_cast<const char*>(fontAddress), static_cast<FT_Long>(subFontIndex), &face);
}
if (error != 0 || !face) {
if (fromMemory || error == FT_Err_Unknown_File_Format) return SCE_FONT_ERROR_NO_SUPPORT_FORMAT;
@ -186,7 +236,7 @@ int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const
obj->next = *inoutFontObj;
if (obj->next) obj->next->prev = obj;
obj->ft_face = face;
obj->ft_ctx_0x58 = &ctx->library;
obj->ft_ctx_0x58 = ftLibrary;
*inoutFontObj = obj;
return SCE_FONT_OK;
}

View File

@ -13,8 +13,13 @@ if(BUILD_TESTING)
target_include_directories(guest_http_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_http_tests PRIVATE libSceHttp)
add_test(NAME guest_http COMMAND guest_http_tests)
add_test_executable(guest_http_response_header_tests ${LIBS_INCLUDE_DIR}/tests/GuestHttpResponseHeader.cpp)
target_include_directories(guest_http_response_header_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_http_response_header_tests PRIVATE libSceHttp libc)
configure_windows_unwind(guest_http_response_header_tests)
add_test(NAME guest_http_response_header COMMAND guest_http_response_header_tests)
if(WIN32)
set_tests_properties(guest_http PROPERTIES ENVIRONMENT_MODIFICATION
set_tests_properties(guest_http guest_http_response_header PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceHttp>")
endif()
endif()

View File

@ -4,6 +4,9 @@
#include "prx/libc/include/General.hpp"
#include "prx/libSceHttp/src/HttpErrors.hpp"
#include <atomic>
#include <limits>
#include <stdexcept>
#include <string_view>
// No network is emulated: contexts, templates and requests can be created, but any request
// that would touch the network fails with the library's network error.
@ -257,9 +260,66 @@ int APS5_VABI sceHttpSetRequestStatusCallback(void) {
return 0;
}
int APS5_VABI sceHttpParseResponseHeader(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
int APS5_VABI sceHttpParseResponseHeader(const char* header, std::size_t headerLen, const char* fieldStr,
const char** fieldValue, std::size_t* valueLen) {
constexpr int invalidResponse = static_cast<int>(0x80432060);
constexpr int invalidValue = static_cast<int>(0x804321FE);
constexpr int notFound = static_cast<int>(0x80432025);
if (!header) return invalidResponse;
if (!fieldStr || !fieldValue || !valueLen) return invalidValue;
std::size_t fieldLen = 0;
while (fieldLen < 0xfff && fieldStr[fieldLen] != '\0') ++fieldLen;
const std::string_view input(header, headerLen);
const auto isSpace = [](char c) { return c == ' ' || (c >= '\t' && c <= '\r'); };
const auto lower = [](char c) { return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; };
std::size_t start = 0;
bool found = false;
while (start < headerLen) {
if (!isSpace(input[start]) && fieldLen < headerLen - start && input[start + fieldLen] == ':') {
std::size_t i = 0;
while (i < fieldLen && lower(input[start + i]) == lower(fieldStr[i])) ++i;
if (i == fieldLen) {
start += fieldLen + 1;
found = true;
break;
}
}
const auto newline = input.find('\n', start);
if (newline == std::string_view::npos) break;
start = newline + 1;
}
if (!found) return notFound;
while (start < headerLen && isSpace(input[start])) {
if (input[start++] == '\n') break;
}
std::size_t end = headerLen;
std::size_t consumed = headerLen;
std::size_t scan = start;
while (scan < headerLen) {
const auto newline = input.find('\n', scan);
if (newline == std::string_view::npos) break;
const auto next = newline + 1;
if (next < headerLen && (input[next] == ' ' || input[next] == '\t')) {
scan = next;
continue;
}
end = newline > start && input[newline - 1] == '\r' ? newline - 1 : newline;
consumed = next;
break;
}
if (end == start) {
*fieldValue = nullptr;
*valueLen = 0;
return 0;
}
if (consumed > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
throw std::out_of_range("sceHttpParseResponseHeader: consumed byte count exceeds int");
}
*fieldValue = header + start;
*valueLen = end - start;
return static_cast<int>(consumed);
}
int APS5_VABI sceHttpCreateRequest2(int conn_id, const char* method, const char* path, uint64_t content_length) {

View File

@ -214,4 +214,24 @@ int APS5_VABI sceHttp2GetMemoryPoolStats() {
return 0;
}
int APS5_VABI sceHttp2CookieFlush(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHttp2CreateCookieBox(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHttp2SetCookieBox(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceHttp2SetRequestNoContentLength(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -18,4 +18,14 @@ if(BUILD_TESTING)
set_tests_properties(guest_ime_parameters PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIme>")
endif()
add_test_executable(guest_ime_keyboard_tests ${LIBS_INCLUDE_DIR}/tests/GuestImeKeyboard.cpp)
target_include_directories(guest_ime_keyboard_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_ime_keyboard_tests PRIVATE libSceIme libc)
configure_windows_unwind(guest_ime_keyboard_tests)
add_test(NAME guest_ime_keyboard COMMAND guest_ime_keyboard_tests)
if(WIN32)
set_tests_properties(guest_ime_keyboard PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceIme>")
endif()
endif()

View File

@ -10,12 +10,18 @@
namespace {
constexpr int ErrorNotOpened = static_cast<int>(0x80bc0002u);
constexpr int ErrorInvalidUserId = static_cast<int>(0x80bc0010u);
constexpr int ErrorInvalidType = static_cast<int>(0x80bc0011u);
constexpr int ErrorInvalidOption = static_cast<int>(0x80bc0015u);
constexpr int ErrorInvalidAddress = static_cast<int>(0x80bc0031u);
constexpr int ErrorNoResourceId = static_cast<int>(0x80bc0023u);
constexpr int ErrorInvalidMode = static_cast<int>(0x80bc0024u);
constexpr uint32_t ValidKeyboardModes = 0x0000007f;
constexpr uint32_t TypeNumber = 4;
constexpr uint32_t ValidOptions = 0x00007bff;
constexpr uint32_t OptionUseOver2K = 0x00004000;
constexpr int32_t UserIdInvalid = -1;
std::mutex g_keyboardMutex;
std::set<int32_t> g_openKeyboards;
@ -47,9 +53,9 @@ int APS5_VABI sceImeKeyboardClose(int32_t user_id) {
int APS5_VABI sceImeKeyboardGetInfo(uint32_t resource_id, KeyboardInfo* info) {
(void)resource_id;
(void)info;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!info) return ErrorInvalidAddress;
std::lock_guard lock(g_keyboardMutex);
return g_openKeyboards.empty() ? ErrorNotOpened : ErrorNoResourceId;
}
int APS5_VABI sceImeKeyboardGetResourceId(int32_t user_id, KeyboardResourceIdArray* resource_ids) {
@ -67,10 +73,10 @@ int APS5_VABI sceImeKeyboardOpen(int32_t user_id, const KeyboardParam* param) {
}
int APS5_VABI sceImeKeyboardSetMode(int32_t user_id, uint32_t mode) {
(void)user_id;
(void)mode;
NotImplemented_nid_no_patch(__func__);
return 0;
if (user_id == UserIdInvalid) return ErrorInvalidUserId;
std::lock_guard lock(g_keyboardMutex);
if (!g_openKeyboards.contains(user_id)) return ErrorNotOpened;
return (mode & ~ValidKeyboardModes) == 0 ? 0 : ErrorInvalidMode;
}
int APS5_VABI sceImeOpen_nid_postfix(const Param* param, const ExtendedParam* extended) {

View File

@ -65,6 +65,24 @@ int APS5_VABI sceMsgDialogUpdateStatus(void) {
return g_status.load();
}
int APS5_VABI sceMsgDialogProgressBarInc(int target, std::uint32_t delta) {
(void)target;
(void)delta;
return g_status.load() == COMMON_DIALOG_STATUS_RUNNING ? 0 : COMMON_DIALOG_ERROR_NOT_RUNNING;
}
int APS5_VABI sceMsgDialogProgressBarSetMsg(int target, const char* msg) {
(void)target;
(void)msg;
return g_status.load() == COMMON_DIALOG_STATUS_RUNNING ? 0 : COMMON_DIALOG_ERROR_NOT_RUNNING;
}
int APS5_VABI sceMsgDialogProgressBarSetValue(int target, std::uint32_t rate) {
(void)target;
(void)rate;
return g_status.load() == COMMON_DIALOG_STATUS_RUNNING ? 0 : COMMON_DIALOG_ERROR_NOT_RUNNING;
}
APS5_EXPORT("CWVW78Qc3fI", sceMsgDialogUnknown00);
int APS5_VABI sceMsgDialogUnknown00(void) {
NotImplemented_nid_no_patch("CWVW78Qc3fI");

View File

@ -49,6 +49,7 @@ constexpr int NET_ECONNABORTED = 53;
constexpr int NET_EMSGSIZE = 40;
constexpr int NET_ETIMEDOUT = 60;
constexpr int NET_ECONNREFUSED = 61;
constexpr int NET_ERROR_BASE = static_cast<int>(0x80410100u);
constexpr int NET_ERROR_RESOLVER_ENODNS = static_cast<int>(0x804101E1u);
constexpr int NET_AF_INET = 2;
@ -65,6 +66,7 @@ constexpr int NET_SO_NBIO = 0x1200;
using NativeSocket = SOCKET;
using NativeLength = int;
constexpr NativeSocket INVALID_NATIVE_SOCKET = INVALID_SOCKET;
constexpr int NATIVE_SEND_FLAGS = 0;
bool initialize_sockets() {
static const int result = [] {
WSADATA data{};
@ -95,6 +97,7 @@ int native_error() {
using NativeSocket = int;
using NativeLength = socklen_t;
constexpr NativeSocket INVALID_NATIVE_SOCKET = -1;
constexpr int NATIVE_SEND_FLAGS = MSG_NOSIGNAL;
bool initialize_sockets() { return true; }
void close_socket(NativeSocket socket) { ::close(socket); }
int native_error() {
@ -115,6 +118,7 @@ int native_error() {
case EINTR: return 4;
case EINVAL: return NET_EINVAL;
case ENOTSOCK: return NET_ENOTSOCK;
case EPIPE: return 32;
default: return 5;
}
}
@ -212,7 +216,7 @@ int* errno_slot() {
int fail(int err) {
*errno_slot() = err;
return -1;
return NET_ERROR_BASE | err;
}
void log_soft(const char* func, const char* what) {
@ -484,7 +488,7 @@ int64_t APS5_VABI sceNetSend(int s, const void* buf, size_t len, int flags) {
if (it == g_socks.end()) return fail(NET_EBADF);
socket = it->second;
}
const int result = ::send(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), 0);
const int result = ::send(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), NATIVE_SEND_FLAGS);
return result >= 0 ? result : fail(native_error());
}
@ -502,13 +506,13 @@ int64_t APS5_VABI sceNetSendto(int s, const void* buf, size_t len, int flags, co
int result;
if (!to) {
if (tolen != 0) return fail(NET_EINVAL);
result = ::send(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), 0);
result = ::send(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), NATIVE_SEND_FLAGS);
} else {
sockaddr_storage destination{};
NativeLength destination_length = 0;
if (!guest_to_native_address(to, tolen, destination, destination_length)) return fail(NET_EINVAL);
if (destination.ss_family != (socket.family == NET_AF_INET ? AF_INET : AF_INET6)) return fail(NET_EAFNOSUPPORT);
result = ::sendto(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), 0,
result = ::sendto(socket.native->value, static_cast<const char*>(buf), static_cast<int>(len), NATIVE_SEND_FLAGS,
reinterpret_cast<const sockaddr*>(&destination), destination_length);
}
return result >= 0 ? result : fail(native_error());
@ -911,4 +915,10 @@ int APS5_VABI sceNetResolverStartAton(int rid, const void* addr, char* hostname,
}
return 0;
}
int APS5_VABI sceNetResolverGetError(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -14,7 +14,7 @@ endif()
if(BUILD_TESTING)
add_test_executable(guest_pad_tests ${LIBS_INCLUDE_DIR}/tests/GuestPad.cpp)
target_include_directories(guest_pad_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_include_directories(guest_pad_tests PRIVATE ${LIBS_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/3rdparty/SDL2/include)
target_link_libraries(guest_pad_tests PRIVATE libScePad libc)
configure_windows_unwind(guest_pad_tests)
add_test(NAME guest_pad COMMAND guest_pad_tests)

View File

@ -233,4 +233,10 @@ int APS5_VABI scePadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enab
return PAD_OK;
}
APS5_EXPORT("fCWdlnmB1Ks", scePadUnknown_fCWdlnmB1Ks);
int APS5_VABI scePadUnknown_fCWdlnmB1Ks(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -1,5 +1,6 @@
#include <cstdint>
#include <cstddef>
#include <cstring>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
@ -7,6 +8,7 @@
namespace {
constexpr std::int32_t ERROR_INVALID_PARAM = static_cast<std::int32_t>(0x81960002);
constexpr std::int32_t ERROR_NOT_SUPPORTED = static_cast<std::int32_t>(0x81960007);
constexpr std::int32_t REQUEST_ID_INVALID = -1;
@ -15,17 +17,19 @@ constexpr std::int32_t REQUEST_ID_INVALID = -1;
extern "C" {
int APS5_VABI sceShareCaptureScreenshot(const void* param, int32_t* req_id) {
(void)param;
(void)req_id;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)param;
if (req_id != nullptr) {
*req_id = REQUEST_ID_INVALID;
}
return ERROR_NOT_SUPPORTED;
}
int APS5_VABI sceShareCaptureVideoClip(const void* param, int32_t* req_id) {
(void)param;
(void)req_id;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)param;
if (req_id != nullptr) {
*req_id = REQUEST_ID_INVALID;
}
return ERROR_NOT_SUPPORTED;
}
int APS5_VABI sceShareFeaturePermit(uint32_t feature_flags) {
@ -39,10 +43,11 @@ int APS5_VABI sceShareFeatureProhibit(uint32_t feature_flags) {
}
int APS5_VABI sceShareGetCurrentStatus(uint32_t feature_flag, ShareCurrentStatus* status) {
(void)feature_flag;
(void)status;
NotImplemented_nid_no_patch(__func__);
return 0;
if (feature_flag == 0 || status == nullptr) {
return ERROR_INVALID_PARAM;
}
std::memset(status, 0, sizeof(*status));
return 0;
}
int APS5_VABI sceShareInitialize(size_t heap_size, int thread_priority, uint64_t affinity_mask) {
@ -53,9 +58,8 @@ int APS5_VABI sceShareInitialize(size_t heap_size, int thread_priority, uint64_t
}
int APS5_VABI sceShareOpenMenuForContent(const void* content_id) {
(void)content_id;
NotImplemented_nid_no_patch(__func__);
return 0;
(void)content_id;
return ERROR_NOT_SUPPORTED;
}
int APS5_VABI sceShareRegisterContentEventCallback(void* callback, void* user_data) {

View File

@ -73,4 +73,34 @@ int APS5_VABI sceSslGetMemoryPoolStats() {
return 0;
}
int APS5_VABI sceSslFreeSslCertName(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSslGetIssuerName(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSslGetNameEntryCount(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSslGetNameEntryInfo(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSslGetPem(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSslGetSubjectName(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -96,8 +96,7 @@ int APS5_VABI sceSystemServiceParamGetString(int paramId, char* buf, size_t bufS
}
int APS5_VABI sceSystemServicePowerTick(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
return SYSTEM_SERVICE_OK;
}
int APS5_VABI sceSystemServiceReceiveEvent(SystemServiceEvent* event) {
@ -109,8 +108,7 @@ int APS5_VABI sceSystemServiceReceiveEvent(SystemServiceEvent* event) {
int APS5_VABI sceSystemServiceReportAbnormalTermination(const void* info) {
(void)info;
NotImplemented_nid_no_patch(__func__);
return 0;
return SYSTEM_SERVICE_OK;
}
int APS5_VABI sceSystemServiceSetNoticeScreenSkipFlag(void) {
@ -137,4 +135,29 @@ int APS5_VABI sceSystemServiceLaunchPlayerDialog(const void* param) {
return SYSTEM_SERVICE_OK;
}
int APS5_VABI sceSystemServiceDisableMusicPlayer(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSystemServiceOpenChallengeActivity(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSystemServiceOpenTournamentOccurrence(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSystemServiceReenableMusicPlayer(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceSystemServiceShowControllerSettings(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -48,11 +48,20 @@ int APS5_VABI sceUserServiceGetAccessibilityZoomEnabled(int user_id, int32_t* zo
return USER_SERVICE_OK;
}
int APS5_VABI sceUserServiceGetAccessibilityZoomFollowFocus(int user_id, int32_t* zoom_follow_focus) {
if (zoom_follow_focus == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
*zoom_follow_focus = 0;
return USER_SERVICE_OK;
}
int APS5_VABI sceUserServiceGetAgeLevel(int user_id, uint32_t* age_level) {
(void)user_id;
(void)age_level;
NotImplemented_nid_no_patch(__func__);
return 0;
if (age_level == nullptr || user_id != USER_SERVICE_INITIAL_USER_ID) {
return USER_SERVICE_ERROR_INVALID_ARGUMENT;
}
*age_level = 0;
return USER_SERVICE_OK;
}
// The initial user is reported as logging in once; afterwards there are no user events.

View File

@ -64,6 +64,7 @@ static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED = 0;
static constexpr int VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED = 1;
static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE = 0;
static constexpr std::uint64_t VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY = 8;
static constexpr std::uint32_t VIDEO_OUT_DCC_CONTROL_BLOCK_LAYOUT = 0x10026c;
static constexpr int VIDEO_OUT_EVENT_FLIP = 0;
static constexpr int VIDEO_OUT_EVENT_VBLANK = 1;

View File

@ -1,3 +1,4 @@
#include <cstdio>
#include <cstring>
#include <mutex>
#include <stdexcept>
@ -12,8 +13,18 @@ AgcDriver::DisplayBuffer DescribeVideoOutBuffer(const VideoOutBuffer& buffer, co
const auto& attribute = group.attribute;
if (attribute.reserved0 != 0 || attribute.pad0 != 0 || attribute.reserved1[0] != 0 || attribute.reserved1[1] != 0 || attribute.reserved1[2] != 0) throw std::runtime_error("VideoOut: reserved buffer attribute bits are set");
if (attribute.tiling_mode > 1 || (attribute.tiling_mode == 0 && attribute.pitch_in_pixel != 0) || attribute.aspect_ratio != 0 || (attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE && attribute.option != VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_STRICT_COLORIMETRY)) throw std::runtime_error("VideoOut: unsupported tiling " + std::to_string(attribute.tiling_mode) + ", pitch " + std::to_string(attribute.pitch_in_pixel) + ", aspect ratio " + std::to_string(attribute.aspect_ratio) + " or buffer option " + std::to_string(attribute.option));
if (group.category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED || buffer.metadataAddress != 0 || attribute.dcc_control != 0 || attribute.dcc_cb_register_clear_color != 0) throw std::runtime_error("VideoOut: DCC presentation is not implemented");
const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height, attribute.tiling_mode, attribute.pitch_in_pixel};
const auto hex = [](std::uint64_t value) {
char text[24];
std::snprintf(text, sizeof(text), "0x%llx", static_cast<unsigned long long>(value));
return std::string(text);
};
const bool compressed = group.category == VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED;
if (!compressed && group.category != VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED) throw std::runtime_error("VideoOut: unknown buffer category " + std::to_string(group.category));
if (!compressed && (buffer.metadataAddress != 0 || attribute.dcc_control != 0 || attribute.dcc_cb_register_clear_color != 0)) throw std::runtime_error("VideoOut: uncompressed buffer with DCC metadata " + hex(buffer.metadataAddress) + ", dcc_control " + hex(attribute.dcc_control) + " or DCC clear color " + hex(attribute.dcc_cb_register_clear_color));
if (compressed && buffer.metadataAddress == 0) throw std::runtime_error("VideoOut: DCC-compressed buffer " + hex(buffer.dataAddress) + " without DCC metadata");
if (compressed && attribute.tiling_mode != 0) throw std::runtime_error("VideoOut: DCC-compressed buffer " + hex(buffer.dataAddress) + " with linear tiling");
if (compressed && (attribute.dcc_control & ~VIDEO_OUT_DCC_CONTROL_BLOCK_LAYOUT) != 0) throw std::runtime_error("VideoOut: unsupported DCC control " + hex(attribute.dcc_control) + " (only the block size and independence fields " + hex(VIDEO_OUT_DCC_CONTROL_BLOCK_LAYOUT) + " are known)");
const AgcDriver::DisplayBuffer result{buffer.dataAddress, attribute.pixel_format, attribute.width, attribute.height, attribute.tiling_mode, attribute.pitch_in_pixel, compressed ? buffer.metadataAddress : 0, compressed ? attribute.dcc_cb_register_clear_color : 0};
static_cast<void>(AgcDriverDisplayBufferSize_nid_postfix(result));
return result;
}

View File

@ -132,6 +132,9 @@ PadInputState PadInput::sampleController() const {
addButton(SDL_CONTROLLER_BUTTON_Y, Pad::PadButton::Triangle);
addButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, Pad::PadButton::L1);
addButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, Pad::PadButton::R1);
const auto type = SDL_GameControllerGetType(controller);
const bool viewPressed = type != SDL_CONTROLLER_TYPE_PS4 && type != SDL_CONTROLLER_TYPE_PS5 && readButton(SDL_CONTROLLER_BUTTON_BACK);
if (viewPressed) result.buttons |= static_cast<std::uint32_t>(Pad::PadButton::TouchPad);
addButton(SDL_CONTROLLER_BUTTON_START, Pad::PadButton::Options);
addButton(SDL_CONTROLLER_BUTTON_LEFTSTICK, Pad::PadButton::L3);
addButton(SDL_CONTROLLER_BUTTON_RIGHTSTICK, Pad::PadButton::R3);
@ -185,6 +188,9 @@ PadInputState PadInput::sampleController() const {
result.touch[finger].y = static_cast<std::uint16_t>(std::clamp(y, 0.0f, 1.0f) * 942.0f);
}
}
if (viewPressed && !result.touch[0].active && !result.touch[1].active) {
result.touch[0] = {true, 960, 471, 0};
}
return result;
}

View File

@ -6,6 +6,7 @@
#include "prx/libSceAgcDriver/Execution/include/Driver.hpp"
#include "prx/libSceAgcDriver/Submit/include/Dcb.hpp"
#include "prx/libc/include/Shutdown.hpp"
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
@ -174,6 +175,26 @@ void testDecode() {
group.attribute.option = option;
check(expectFailure([&] { DescribeVideoOutBuffer(registered, group); }).find("buffer option " + std::to_string(option)) != std::string::npos, "an unsupported buffer option was accepted");
}
VideoOutBuffer dccBuffer{0, reinterpret_cast<uint64_t>(tiled.data()), 0x7f0000};
sceVideoOutSetBufferAttribute2(&group.attribute, 0x8000000000000000ull, 0, 259, 137, VIDEO_OUT_BUFFER_ATTRIBUTE_OPTION_NONE, 0x208, 0x11223344);
group.category = VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED;
const auto compressed = DescribeVideoOutBuffer(dccBuffer, group);
check(compressed.address == dccBuffer.dataAddress && compressed.dccAddress == 0x7f0000 && compressed.dccClearColor == 0x11223344, "DCC metadata or clear color was not described");
group.attribute.dcc_control = 0x100024;
check(DescribeVideoOutBuffer(dccBuffer, group).dccAddress == 0x7f0000, "independent 128-byte DCC blocks were rejected");
group.attribute.dcc_control = 0x209;
check(expectFailure([&] { DescribeVideoOutBuffer(dccBuffer, group); }).find("DCC control 0x209") != std::string::npos, "an unknown DCC control bit was accepted");
group.attribute.dcc_control = 0x208;
group.attribute.tiling_mode = 1;
check(expectFailure([&] { DescribeVideoOutBuffer(dccBuffer, group); }).find("linear") != std::string::npos, "a linear DCC buffer was accepted");
group.attribute.tiling_mode = 0;
dccBuffer.metadataAddress = 0;
check(expectFailure([&] { DescribeVideoOutBuffer(dccBuffer, group); }).find("without DCC metadata") != std::string::npos, "a compressed buffer without metadata was accepted");
dccBuffer.metadataAddress = 0x7f0000;
group.category = VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_UNCOMPRESSED;
check(expectFailure([&] { DescribeVideoOutBuffer(dccBuffer, group); }).find("DCC metadata 0x7f0000") != std::string::npos, "an uncompressed buffer with DCC metadata was accepted");
group.category = 2;
check(expectFailure([&] { DescribeVideoOutBuffer(dccBuffer, group); }).find("category 2") != std::string::npos, "an unknown buffer category was accepted");
}
void testOpenParam() {
@ -284,6 +305,36 @@ void testPresentation(bool expectUnavailable) {
LibcRunShutdown_nid_postfix();
}
void testCompressedPresentation() {
const int handle = sceVideoOutOpen(255, 0, 0, nullptr);
auto cfg = VideoOutDriver::Get().GetConfig(handle);
std::vector<std::byte> allocation(6 * 65536 + 65535);
const auto storage = alignedBuffer(allocation);
fillBuffer(storage, 259, 137);
std::vector<std::uint8_t> keys(storage.size() / 256);
VideoOutBuffers buffer{storage.data(), keys.data(), {nullptr, nullptr}};
VideoOutBufferAttribute2 attribute{};
sceVideoOutSetBufferAttribute2(&attribute, 0x8000000000000000ull, 0, 259, 137, 0, 0x208, 0xff102030);
sceVideoOutRegisterBuffers2(handle, 0, 0, &buffer, 1, &attribute, VIDEO_OUT_BUFFER_ATTRIBUTE_CATEGORY_COMPRESSED, nullptr);
int64_t argument = 2000;
for (std::uint8_t key : {0x00, 0xff, 0x20, 0xc0}) {
std::fill(keys.begin(), keys.end(), key);
uint64_t target;
{
std::lock_guard lock(cfg->mutex);
target = cfg->flipStatus.count + 1;
}
sceVideoOutSubmitFlip(handle, 0, 1, ++argument);
std::unique_lock lock(cfg->mutex);
check(cfg->vblankCond.wait_for(lock, std::chrono::seconds(15), [&] { return cfg->failure || cfg->flipStatus.count == target; }), "DCC presentation did not complete");
if (cfg->failure) std::rethrow_exception(cfg->failure);
check(cfg->flipStatus.flipArg == argument && cfg->flipStatus.flipPendingNum == 0, "DCC flip status is wrong");
}
sceVideoOutUnregisterBuffers(handle, 0);
sceVideoOutClose(handle);
LibcRunShutdown_nid_postfix();
}
// Flips of two buffers submitted without waiting for each other: every one completes in order
// (presentations trail on the GPU, APS5_FLIP_INFLIGHT), and the shutdown retires the ones in flight.
void testBackToBack() {
@ -426,6 +477,7 @@ int main(int argc, char** argv) {
else if (argc == 2 && std::string(argv[1]) == "controls") testControls();
else if (argc == 2 && std::string(argv[1]) == "present") testPresentation(false);
else if (argc == 2 && std::string(argv[1]) == "backtoback") testBackToBack();
else if (argc == 2 && std::string(argv[1]) == "dcc") testCompressedPresentation();
else if (argc == 2 && std::string(argv[1]) == "pacing") testReleaseVblank();
else if (argc == 2 && std::string(argv[1]) == "aftergpu") testFlipAfterGpuWork();
else if (argc == 2 && std::string(argv[1]) == "unavailable") testPresentation(true);

View File

@ -0,0 +1,21 @@
add_library(libSceVoiceQoS SHARED EXCLUDE_FROM_ALL Export.cpp)
target_include_directories(libSceVoiceQoS PRIVATE ${LIBS_INCLUDE_DIR})
configure_windows_unwind(libSceVoiceQoS)
if(MINGW)
foreach(runtimeLinkFlag IN ITEMS -static-libgcc -static-libstdc++ -static)
string(REPLACE "${runtimeLinkFlag}" "" CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
endforeach()
endif()
if(BUILD_TESTING)
add_test_executable(guest_voice_qos_tests ${LIBS_INCLUDE_DIR}/tests/GuestVoiceQoS.cpp)
target_include_directories(guest_voice_qos_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_voice_qos_tests PRIVATE libSceVoiceQoS libc)
configure_windows_unwind(guest_voice_qos_tests)
add_test(NAME guest_voice_qos COMMAND guest_voice_qos_tests)
if(WIN32)
set_tests_properties(guest_voice_qos PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libSceVoiceQoS>")
endif()
endif()

View File

@ -1,16 +1,29 @@
#include <cstdint>
#include <cstddef>
#include <mutex>
#include <stdexcept>
#include <string>
#include "SceTypes.hpp"
#include "prx/libc/include/General.hpp"
namespace {
constexpr std::int32_t VOICE_QOS_APP_TYPE_GAME = 0x20000000;
std::mutex g_mutex;
bool g_initialized = false;
}
extern "C" {
int APS5_VABI sceVoiceQoSInit(void* mem_block, uint32_t mem_size, int32_t app_type) {
(void)mem_block;
(void)mem_size;
(void)app_type;
NotImplemented_nid_no_patch(__func__);
return 0;
if (!mem_block || mem_size == 0) throw std::invalid_argument("sceVoiceQoSInit: null or empty memory block");
if (app_type != VOICE_QOS_APP_TYPE_GAME) throw std::invalid_argument("sceVoiceQoSInit: unsupported app type " + std::to_string(app_type));
std::lock_guard lock(g_mutex);
if (g_initialized) throw std::runtime_error("sceVoiceQoSInit: already initialized");
g_initialized = true;
return 0;
}
int APS5_VABI sceVoiceQoSEnd() {

View File

@ -77,6 +77,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_math PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_execute_once_tests ${LIBS_INCLUDE_DIR}/tests/GuestExecuteOnce.cpp)
target_include_directories(guest_execute_once_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_execute_once_tests PRIVATE libc)
configure_windows_unwind(guest_execute_once_tests)
add_test(NAME guest_execute_once COMMAND guest_execute_once_tests)
set_tests_properties(guest_execute_once PROPERTIES TIMEOUT 20)
if(WIN32)
set_tests_properties(guest_execute_once PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_working_directory_tests ${LIBS_INCLUDE_DIR}/tests/GuestWorkingDirectory.cpp)
target_include_directories(guest_working_directory_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_working_directory_tests PRIVATE libc)
@ -176,6 +186,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_strings PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_wcsrtombs_s_tests ${LIBS_INCLUDE_DIR}/tests/GuestWcsrtombsS.cpp)
target_include_directories(guest_wcsrtombs_s_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_wcsrtombs_s_tests PRIVATE libc)
configure_windows_unwind(guest_wcsrtombs_s_tests)
add_test(NAME guest_wcsrtombs_s COMMAND guest_wcsrtombs_s_tests)
if(WIN32)
set_tests_properties(guest_wcsrtombs_s PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
if(UNIX)
add_test_executable(exception_runtime_tests ${LIBS_INCLUDE_DIR}/tests/ExceptionRuntime.cpp)
target_include_directories(exception_runtime_tests PRIVATE ${LIBS_INCLUDE_DIR})

View File

@ -38,15 +38,6 @@ int APS5_VABI libcUnknown_u2tMGOLaqnE() {
return 0;
}
// Live float->float import of Cyberpunk 2077 (unnamed); callers pass one
// float in xmm0 and consume the result, so return 0.0f instead of integer 0.
APS5_EXPORT("DQbtGaBKlaw", libcCyberUnknown17);
float APS5_VABI libcCyberUnknown17(float x) {
(void)x;
NotImplemented_nid_no_patch("DQbtGaBKlaw");
return 0.0f;
}
// Live Cyberpunk 2077 import used in an fopen/fseek/ftell-like file-size
// idiom as (handle, 0, 2); returning 0 reports success.
APS5_EXPORT("rWSuTWY2JN0", libcCyberUnknown18);
@ -64,12 +55,6 @@ int APS5_VABI libcCyberUnknown19(void) {
}
APS5_EXPORT("DiGVep5yB5w", libcCyberUnknown01);
std::uint64_t APS5_VABI libcCyberUnknown01(void) {
NotImplemented_nid_no_patch("DiGVep5yB5w");
return 0;
}
APS5_EXPORT("Ye20uNnlglA", libcCyberUnknown02);
std::uint64_t APS5_VABI libcCyberUnknown02(void) {
NotImplemented_nid_no_patch("Ye20uNnlglA");
@ -82,24 +67,12 @@ std::uint64_t APS5_VABI libcCyberUnknown03(void) {
return 0;
}
APS5_EXPORT("79s2tnYQI6I", libcCyberUnknown04);
std::uint64_t APS5_VABI libcCyberUnknown04(void) {
NotImplemented_nid_no_patch("79s2tnYQI6I");
return 0;
}
APS5_EXPORT("7yMFgcS8EPA", libcCyberUnknown05);
std::uint64_t APS5_VABI libcCyberUnknown05(void) {
NotImplemented_nid_no_patch("7yMFgcS8EPA");
return 0;
}
APS5_EXPORT("Cj+Fw5q1tUo", libcCyberUnknown06);
std::uint64_t APS5_VABI libcCyberUnknown06(void) {
NotImplemented_nid_no_patch("Cj+Fw5q1tUo");
return 0;
}
APS5_EXPORT("CyXs2l-1kNA", libcCyberUnknown07);
std::uint64_t APS5_VABI libcCyberUnknown07(void) {
NotImplemented_nid_no_patch("CyXs2l-1kNA");

View File

@ -16,6 +16,7 @@
#include <locale>
#include <regex>
#include <functional>
#include <mutex>
#include "prx/libc/include/General.hpp"
@ -40,4 +41,15 @@ int APS5_VABI LibcInternalExtCxaThreadAtexit_nid_postfix(void (*destructor)(void
const std::error_category* _ZSt17iostream_categoryv_nid_postfix() { return &std::iostream_category(); }
int APS5_VABI _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(
int* flag, int (APS5_VABI *callback)(void*, void*, void**), void* arg
) {
static std::recursive_mutex mutex;
std::lock_guard lock(mutex);
if (*flag != 0) return 1;
if (callback(nullptr, arg, nullptr) == 0) return 0;
*flag = 1;
return 1;
}
}

View File

@ -3,6 +3,7 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <climits>
extern "C" {
@ -47,6 +48,78 @@ std::size_t APS5_VABI wcrtomb_nid_postfix(char* destination, std::uint16_t value
return 1;
}
int APS5_VABI wcsrtombs_s_nid_postfix(
std::size_t* result, char* destination, std::size_t capacity, const std::uint16_t** source, std::size_t limit,
void* state
) {
constexpr int GuestEinval = 22;
constexpr int GuestErange = 34;
constexpr int GuestEilseq = 86;
constexpr auto Failed = static_cast<std::size_t>(-1);
if (!result || !source || !state || !*source || (destination && (static_cast<std::int64_t>(capacity) <= 0
|| static_cast<std::int64_t>(limit) < 0))) {
if (destination && static_cast<std::int64_t>(capacity) > 0) destination[0] = '\0';
if (result) *result = Failed;
return GuestEinval;
}
if (!destination && capacity) {
*result = Failed;
return GuestEinval;
}
const auto* wide = *source;
char bytes[MB_LEN_MAX];
if (!destination) {
std::size_t total = 0;
for (;; ++wide) {
const auto length = wcrtomb_nid_postfix(bytes, *wide, state);
if (length == Failed) {
*result = Failed;
return GuestEilseq;
}
if (length && bytes[length - 1] == '\0') {
*result = total + length - 1;
return 0;
}
total += length;
}
}
std::size_t written = 0;
while (limit) {
const auto length = wcrtomb_nid_postfix(bytes, *wide, state);
if (length == Failed) {
destination[written] = '\0';
*result = Failed;
return GuestEilseq;
}
if (length > limit || length > capacity) {
*source = wide;
destination[0] = '\0';
*result = Failed;
return GuestErange;
}
std::memcpy(destination + written, bytes, length);
written += length;
if (length && destination[written - 1] == '\0') {
*source = nullptr;
*result = written - 1;
return 0;
}
++wide;
capacity -= length;
if (!capacity) {
*source = wide;
destination[0] = '\0';
*result = Failed;
return GuestErange;
}
limit -= length;
}
*source = wide;
destination[written] = '\0';
*result = written;
return 0;
}
std::size_t APS5_VABI mbsrtowcs_nid_postfix(std::uint16_t* destination, const char** source, std::size_t capacity, void*) {
if (!destination) return std::strlen(*source);
std::size_t converted = 0;

View File

@ -26,6 +26,10 @@ std::size_t APS5_VABI strnlen_nid_postfix(const char* text, std::size_t limit) {
return length;
}
std::size_t APS5_VABI strnlen_s_nid_postfix(const char* text, std::size_t limit) {
return text ? strnlen_nid_postfix(text, limit) : 0;
}
char* APS5_VABI strncat_nid_postfix(char* destination, const char* source, std::size_t limit) {
return std::strncat(destination, source, limit);
}

View File

@ -21,6 +21,19 @@ int64_t APS5_VABI time_nid_postfix(int64_t* timer) {
return libc_time_nid_postfix(timer);
}
int64_t APS5_VABI _Xtime_get_ticks_nid_postfix() {
#ifdef _WIN32
FILETIME ft{};
GetSystemTimePreciseAsFileTime(&ft);
const uint64_t t = ((static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime) - 116444736000000000ULL;
return static_cast<int64_t>(t / 10);
#else
timespec now{};
clock_gettime(CLOCK_REALTIME, &now);
return static_cast<int64_t>(now.tv_sec) * 1000000 + now.tv_nsec / 1000;
#endif
}
double APS5_VABI libc_difftime_nid_postfix(int64_t time1, int64_t time0) {
return std::difftime(static_cast<std::time_t>(time1), static_cast<std::time_t>(time0));
}

View File

@ -249,12 +249,21 @@ ParsedParamJson parseParamJson(const std::filesystem::path& paramJsonPath) {
if (localizedParametersValue->type != JsonType::Object) throw std::runtime_error("param.json localizedParameters is not an object");
if (localizedParametersValue->objectValue.empty()) throw std::runtime_error("param.json localizedParameters is empty");
std::string selectedLanguage;
const JsonValue* defaultLanguageValue = findObjectMember(root, "defaultLanguage");
const JsonValue* defaultLanguageValue = findObjectMember(*localizedParametersValue, "defaultLanguage");
if (defaultLanguageValue == nullptr) defaultLanguageValue = findObjectMember(root, "defaultLanguage");
if (defaultLanguageValue != nullptr) selectedLanguage = asString(*defaultLanguageValue, "defaultLanguage");
const JsonValue* languageObject = nullptr;
if (!selectedLanguage.empty()) languageObject = findObjectMember(*localizedParametersValue, selectedLanguage);
if (languageObject == nullptr) languageObject = findObjectMember(*localizedParametersValue, "en-US");
if (languageObject == nullptr) languageObject = &localizedParametersValue->objectValue.front().second;
if (languageObject == nullptr) {
for (const auto& member : localizedParametersValue->objectValue) {
if (member.second.type == JsonType::Object) {
languageObject = &member.second;
break;
}
}
}
if (languageObject == nullptr) throw std::runtime_error("param.json localizedParameters has no language entry");
const JsonValue* titleValue = findObjectMember(*languageObject, "titleName");
if (titleValue == nullptr) throw std::runtime_error("param.json localized entry is missing titleName");
const std::string title = asString(*titleValue, "titleName");

View File

@ -8,7 +8,7 @@
// The guest treats both the command buffer object and its memory as opaque, so the encoding is ours.
namespace Apr {
enum class BufferType : std::uint32_t {
enum class BufferType : std::uint16_t {
Generic = 0,
Apr = 1,
};
@ -19,9 +19,12 @@ struct CommandBufferObject {
std::uint32_t offset;
std::uint32_t numCommands;
BufferType type;
std::uint16_t recording;
};
static_assert(sizeof(CommandBufferObject) == 0x18, "guest reserves 0x18 bytes for sce::Ampr::CommandBuffer");
inline constexpr std::uint16_t ScatterGatherValid = 1;
enum class Opcode : std::uint32_t {
Nop = 0,
ReadFile = 1,
@ -36,6 +39,10 @@ enum class Opcode : std::uint32_t {
PushMarker = 10,
PopMarker = 11,
SetMarker = 12,
ReadFileGather = 13,
ReadFileScatter = 14,
ReadFileGatherScatter = 15,
ResetGatherScatterState = 16,
};
struct CommandHeader {
@ -52,6 +59,10 @@ struct ReadFileCommand {
std::uint64_t offset;
};
struct ResetGatherScatterStateCommand {
CommandHeader header;
};
struct WriteAddressCommand {
CommandHeader header;
std::uint64_t address;
@ -60,10 +71,32 @@ struct WriteAddressCommand {
std::uint32_t reserved;
};
enum class CounterAccess : std::uint8_t {
Size8 = 0,
Size4 = 1,
Size2Offset0 = 2,
Size2Offset1 = 3,
Size1Offset0 = 4,
Size1Offset1 = 5,
Size1Offset2 = 6,
Size1Offset3 = 7,
};
enum class CounterOperation : std::uint8_t {
Store = 0,
AtomicOr = 1,
AtomicAndComplement = 2,
AtomicXor = 3,
AtomicAdd = 4,
};
struct WriteCounterCommand {
CommandHeader header;
std::uint32_t counter;
std::uint32_t value;
CounterAccess access;
CounterOperation operation;
std::uint16_t reserved;
std::uint64_t value;
};
struct WaitCommand {
@ -72,7 +105,9 @@ struct WaitCommand {
std::uint64_t reference;
std::uint64_t mask;
std::uint32_t counter;
std::uint32_t compare;
std::uint8_t compare;
CounterAccess access;
std::uint16_t reserved;
};
struct WriteKernelEventQueueCommand {

View File

@ -161,10 +161,63 @@ void _writeAddress(const Apr::WriteAddressCommand& command) {
std::array<std::atomic<std::uint32_t>, 256> g_counters{};
std::mutex g_counterWrites;
std::uint32_t _counter(std::uint32_t index) {
return g_counters[index % g_counters.size()].load(std::memory_order_acquire);
}
struct CounterField {
std::uint32_t bits;
std::uint32_t shift;
};
CounterField _counterField(Apr::CounterAccess access) {
const auto value = static_cast<std::uint32_t>(access);
if (value == 0) return {64, 0};
if (value == 1) return {32, 0};
if (value < 4) return {16, (value - 2) * 16};
if (value < 8) return {8, (value - 4) * 8};
throw std::runtime_error("APR: counter access " + std::to_string(value) + " not implemented");
}
std::uint64_t _fieldMask(std::uint32_t bits) {
return bits == 64 ? ~std::uint64_t{0} : (std::uint64_t{1} << bits) - 1u;
}
std::uint64_t _readCounter(std::uint32_t index, Apr::CounterAccess access) {
const auto field = _counterField(access);
if (field.bits == 64) return _counter(index) | static_cast<std::uint64_t>(_counter(index + 1u)) << 32u;
return (_counter(index) >> field.shift) & _fieldMask(field.bits);
}
std::uint64_t _applyCounterOperation(Apr::CounterOperation operation, std::uint64_t current, std::uint64_t value) {
switch (operation) {
case Apr::CounterOperation::Store: return value;
case Apr::CounterOperation::AtomicOr: return current | value;
case Apr::CounterOperation::AtomicAndComplement: return current & ~value;
case Apr::CounterOperation::AtomicXor: return current ^ value;
case Apr::CounterOperation::AtomicAdd: return current + value;
}
throw std::runtime_error("APR: counter operation " + std::to_string(static_cast<std::uint32_t>(operation)) + " not implemented");
}
void _writeCounter(const Apr::WriteCounterCommand& command) {
const auto field = _counterField(command.access);
const std::lock_guard lock(g_counterWrites);
const std::uint64_t current = _readCounter(command.counter, command.access);
const std::uint64_t next = _applyCounterOperation(command.operation, current, command.value) & _fieldMask(field.bits);
auto& low = g_counters[command.counter % g_counters.size()];
if (field.bits == 64) {
g_counters[(command.counter + 1u) % g_counters.size()].store(static_cast<std::uint32_t>(next >> 32u), std::memory_order_release);
low.store(static_cast<std::uint32_t>(next), std::memory_order_release);
return;
}
const auto mask = static_cast<std::uint32_t>(_fieldMask(field.bits) << field.shift);
const auto bits = static_cast<std::uint32_t>(next << field.shift);
low.store((low.load(std::memory_order_relaxed) & ~mask) | bits, std::memory_order_release);
}
bool _waitSatisfied(std::uint32_t compare, std::uint64_t value, std::uint64_t reference) {
constexpr std::uint64_t sign = std::uint64_t{1} << 63u;
switch (compare) {
@ -186,8 +239,27 @@ TCommand _read(const Apr::CommandBufferObject& buffer, std::uint32_t cursor) {
return command;
}
struct ReadCursor {
bool valid = false;
std::uint32_t fileId = 0;
std::uint64_t nextDestination = 0;
std::uint64_t nextOffset = 0;
};
void _readResolved(Apr::Opcode opcode, Apr::ReadFileCommand command, ReadCursor& read) {
if (opcode != Apr::Opcode::ReadFile) {
if (!read.valid) throw std::runtime_error("APR: gather or scatter read without a preceding read");
command.fileId = read.fileId;
if (opcode == Apr::Opcode::ReadFileGather) command.destination = read.nextDestination;
if (opcode == Apr::Opcode::ReadFileScatter) command.offset = read.nextOffset;
}
_readFile(command);
read = {true, command.fileId, command.destination + command.size, command.offset + command.size};
}
void _execute(const Apr::CommandBufferObject& buffer) {
std::uint32_t cursor = 0;
ReadCursor read;
for (std::uint32_t index = 0; index < buffer.numCommands; ++index) {
if (cursor + sizeof(Apr::CommandHeader) > buffer.offset) throw std::runtime_error("APR: truncated command buffer");
Apr::CommandHeader header;
@ -199,12 +271,15 @@ void _execute(const Apr::CommandBufferObject& buffer) {
case Apr::Opcode::PopMarker:
case Apr::Opcode::SetMarker:
break;
case Apr::Opcode::ReadFile: {
Apr::ReadFileCommand command;
std::memcpy(&command, buffer.base + cursor, sizeof(command));
_readFile(command);
case Apr::Opcode::ReadFile:
case Apr::Opcode::ReadFileGather:
case Apr::Opcode::ReadFileScatter:
case Apr::Opcode::ReadFileGatherScatter:
_readResolved(header.opcode, _read<Apr::ReadFileCommand>(buffer, cursor), read);
break;
case Apr::Opcode::ResetGatherScatterState:
read = {};
break;
}
case Apr::Opcode::WriteAddress: {
Apr::WriteAddressCommand command;
std::memcpy(&command, buffer.base + cursor, sizeof(command));
@ -212,18 +287,20 @@ void _execute(const Apr::CommandBufferObject& buffer) {
break;
}
case Apr::Opcode::WriteCounter: {
const auto command = _read<Apr::WriteCounterCommand>(buffer, cursor);
g_counters[command.counter % g_counters.size()].store(command.value, std::memory_order_release);
_writeCounter(_read<Apr::WriteCounterCommand>(buffer, cursor));
break;
}
case Apr::Opcode::WaitOnAddress:
case Apr::Opcode::WaitOnCounter: {
const auto command = _read<Apr::WaitCommand>(buffer, cursor);
const bool counter = header.opcode == Apr::Opcode::WaitOnCounter;
const std::uint32_t unused = counter ? 64u - _counterField(command.access).bits : 0u;
const auto current = [&]() -> std::uint64_t {
if (header.opcode == Apr::Opcode::WaitOnCounter) return _counter(command.counter);
if (counter) return _readCounter(command.counter, command.access);
return std::atomic_ref<std::uint64_t>(*reinterpret_cast<std::uint64_t*>(command.address)).load(std::memory_order_acquire);
};
while (!_waitSatisfied(command.compare, current() & command.mask, command.reference & command.mask)) std::this_thread::sleep_for(std::chrono::microseconds(50));
const std::uint64_t reference = (command.reference & command.mask) << unused;
while (!_waitSatisfied(command.compare, (current() & command.mask) << unused, reference)) std::this_thread::sleep_for(std::chrono::microseconds(50));
break;
}
case Apr::Opcode::WriteKernelEventQueue: {

View File

@ -487,4 +487,14 @@ int APS5_VABI sceKernelUtimes_nid_postfix(const char* path, const KernelTimeval*
return 0;
}
int APS5_VABI utimes_nid_postfix(const char* path, const KernelTimeval* times) {
if (const int error = PathError(path)) return PosixFailure(error);
return PosixResult(sceKernelUtimes_nid_postfix(path, times));
}
int APS5_VABI fsync_nid_postfix(int fd) {
if (sceKernelFsync(fd) != 0) return PosixFailure(errno);
return 0;
}
}

View File

@ -1,6 +1,7 @@
#include "prx/libc/include/General.hpp"
#include <nid/NidCompute.hpp>
#include <array>
#include <filesystem>
#include <cstdio>
#include <map>
#include <memory>
@ -57,6 +58,13 @@ char* APS5_VABI dlerror_nid_postfix() {
pendingError = false;
return loaderError.data();
}
static std::filesystem::path RelinkedModulePath(const std::filesystem::path& path) {
auto relinked = path;
relinked += ".guest.prx";
std::error_code error;
return std::filesystem::is_regular_file(relinked, error) ? relinked : path;
}
void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) {
if ((flags & ~0x103) || (flags & 3) == 0 || (flags & 3) == 3) {
Error("dlopen: unsupported flags"); return nullptr;
@ -70,7 +78,7 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) {
module->owned = false;
} else {
if (!*path) { Error("dlopen: empty module path"); return nullptr; }
const auto resolved = ResolvePath_nid_no_patch(path);
const auto resolved = RelinkedModulePath(ResolvePath_nid_no_patch(path));
module->native = LoadLibraryExW(resolved.c_str(), nullptr, LOAD_WITH_ALTERED_SEARCH_PATH);
}
if (!module->native) {
@ -79,7 +87,7 @@ void* APS5_VABI dlopen_nid_postfix(const char* path, int flags) {
Error(message); return nullptr;
}
#else
const auto resolved = path ? ResolvePath_nid_no_patch(path).string() : std::string{};
const auto resolved = path ? RelinkedModulePath(ResolvePath_nid_no_patch(path)).string() : std::string{};
const int nativeFlags = ((flags & 3) == 1 ? RTLD_LAZY : RTLD_NOW) |
((flags & 0x100) ? RTLD_GLOBAL : RTLD_LOCAL);
module->native = ::dlopen(path ? resolved.c_str() : nullptr, nativeFlags);

View File

@ -5,6 +5,7 @@
#include "prx/libc/include/General.hpp"
#include "../include/Pthread.hpp"
#include "Common.hpp"
#include "prx/libkernel/KernelErrors.hpp"
#include <thread>
extern "C" {
@ -39,6 +40,18 @@ void APS5_VABI pthread_exit_nid_postfix(void* value) {
scePthreadExit(value);
}
int APS5_VABI scePthreadGetschedparam(Pthread thread, int* policy, KernelSchedParam* param) {
if (!policy || !param) return SCE_KERNEL_ERROR_EINVAL;
*policy = GUEST_SCHED_FIFO;
return scePthreadGetprio(thread, &param->sched_priority);
}
int APS5_VABI scePthreadSetschedparam(Pthread thread, int policy, const KernelSchedParam* param) {
(void)policy;
if (!param) return SCE_KERNEL_ERROR_EINVAL;
return scePthreadSetprio(thread, param->sched_priority);
}
int APS5_VABI pthread_getschedparam_nid_postfix(Pthread thread, int* policy, KernelSchedParam* param) {
if (!policy || !param) return PosixThread::GUEST_EINVAL;
*policy = GUEST_SCHED_FIFO;

View File

@ -42,6 +42,27 @@ static std::atomic<get_thread_atexit_count_func_t> threadAtexitCount{nullptr};
static std::atomic<thread_atexit_report_func_t> threadAtexitReport{nullptr};
static thread_local PthreadPrivate* currentThread = nullptr;
static thread_local bool threadFinishing = false;
static void SetStackFromHost(PthreadPrivate* thread) {
#ifdef _WIN32
ULONG_PTR low = 0;
ULONG_PTR high = 0;
GetCurrentThreadStackLimits(&low, &high);
if (high <= low) throw std::runtime_error("Cannot query the host thread stack");
thread->stackAddress = reinterpret_cast<void*>(low);
thread->stackSize = static_cast<std::size_t>(high - low);
#else
pthread_attr_t attr;
if (pthread_getattr_np(pthread_self(), &attr) != 0) throw std::runtime_error("Cannot query the host thread stack");
void* address = nullptr;
std::size_t size = 0;
const int result = pthread_attr_getstack(&attr, &address, &size);
pthread_attr_destroy(&attr);
if (result != 0 || size == 0) throw std::runtime_error("Cannot query the host thread stack");
thread->stackAddress = address;
thread->stackSize = size;
#endif
}
#ifndef _WIN32
static thread_local std::unique_ptr<PthreadPrivate> adoptedThread;
// pthread_exit force-unwinds through guest frames, whose personality resolves to
@ -96,6 +117,7 @@ static void RunThread(std::unique_ptr<ThreadArgs> args) {
const auto entry = args->entry;
void* arg = args->arg;
PthreadPrivate* self = args->self;
if (!self->stackAddress) SetStackFromHost(self);
currentThread = self;
args.reset();
finishThread(self, entry(arg));
@ -327,6 +349,7 @@ Pthread APS5_VABI scePthreadSelf() {
adopted->threadId = std::this_thread::get_id();
adopted->_detached = true;
adopted->references.store(1, std::memory_order_relaxed);
SetStackFromHost(adopted.get());
currentThread = adopted.release();
}
#else
@ -335,6 +358,7 @@ Pthread APS5_VABI scePthreadSelf() {
adoptedThread->_detached = true;
adoptedThread->_adopted = true;
adoptedThread->threadId = std::this_thread::get_id();
SetStackFromHost(adoptedThread.get());
currentThread = adoptedThread.get();
}
#endif

View File

@ -57,6 +57,7 @@ int NativeError() {
case EAGAIN: return 35;
case EADDRINUSE: return 48;
case EADDRNOTAVAIL: return 49;
case EACCES: return 13;
case EMSGSIZE: return 40;
case ENETUNREACH: return 51;
case EHOSTUNREACH: return 65;
@ -65,6 +66,10 @@ int NativeError() {
case ECONNABORTED: return 53;
case EISCONN: return 56;
case ENOTCONN: return 57;
case ENOBUFS: return 55;
case ETIMEDOUT: return 60;
case EINTR: return 4;
case EINVAL: return 22;
default: return 5;
}
#endif

View File

@ -321,4 +321,12 @@ int APS5_VABI sceKernelIsTrinityMode(void) {
return 0;
}
int APS5_VABI sceKernelGetOperationMode(int* mode, int* submode) {
if (!mode || !submode)
throw std::invalid_argument("sceKernelGetOperationMode: null output");
*mode = 0;
*submode = 0;
return 0;
}
}

View File

@ -27,4 +27,28 @@ int APS5_VABI pthread_cancel_nid_postfix(void) {
NotImplemented_nid_no_patch("0D4-FVvEikw");
return 0;
}
int APS5_VABI sceKernelFchmod(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI fchmod_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI futimes_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI recvmsg_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sendmsg_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
}

View File

@ -25,6 +25,7 @@ int APS5_VABI sceAjmBatchJobGetGaplessDecode(AjmBatchInfo*, std::uint32_t, void*
int APS5_VABI sceAjmBatchJobGetCodecInfo(AjmBatchInfo*, std::uint32_t, void*, std::size_t);
int APS5_VABI sceAjmBatchStart(std::uint32_t, const AjmBatchInfo*, int, AjmBatchError*, std::uint32_t*);
int APS5_VABI sceAjmBatchWait(std::uint32_t, std::uint32_t, std::uint32_t, AjmBatchError*);
int APS5_VABI sceAjmBatchCancel(std::uint32_t, std::uint32_t);
}
static void Require(bool value) { if (!value) std::abort(); }
@ -201,6 +202,111 @@ void TestMp3ParseFrame() {
Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter);
}
const std::uint8_t LAME_MPEG1_STEREO_VBR[] = {
0xFF, 0xFB, 0x90, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x69, 0x6E, 0x67, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0A,
0x00, 0x00, 0x09, 0x28, 0x00, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E, 0x6E,
0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x8D, 0x9F, 0x9F, 0x9F, 0x9F,
0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xAC, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE,
0xBE, 0xBE, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x0A, 0x4C, 0x41, 0x4D, 0x45, 0x34, 0x2E, 0x30, 0x20, 0x00, 0x04, 0x48, 0x00,
0x00, 0x00, 0x00, 0x2E, 0x1A, 0x00, 0x00, 0x15, 0x20, 0x24, 0x03, 0xB0, 0x45, 0x00, 0x01, 0x9A, 0x00, 0x00, 0x09, 0x28, 0x06, 0x5C, 0x53, 0xC5,
};
const std::uint8_t LAME_MPEG1_MONO_CBR[] = {
0xFF, 0xFB, 0x98, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x6E, 0x66,
0x6F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x18, 0xC0, 0x00, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x33,
0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x66, 0x66, 0x66, 0x66, 0x66,
0x66, 0x66, 0x66, 0x66, 0x66, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
0x99, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xE6, 0xE6, 0xE6,
0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x38, 0x4C, 0x41, 0x4D,
0x45, 0x34, 0x2E, 0x30, 0x20, 0x00, 0x01, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x15, 0x00, 0x00, 0x14, 0x80, 0x24, 0x03, 0xB0, 0x22, 0x00, 0x00,
0x80, 0x00, 0x00, 0x18, 0xC0, 0x9A, 0x66, 0xF0, 0x95,
};
const std::uint8_t LAME_MPEG2_MONO_CBR[] = {
0xFF, 0xF3, 0x80, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x6E, 0x66, 0x6F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
0x14, 0x00, 0x00, 0x11, 0x23, 0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0x19, 0x19, 0x19, 0x19, 0x19, 0x26, 0x26, 0x26, 0x26, 0x26, 0x33, 0x33, 0x33, 0x33,
0x33, 0x40, 0x40, 0x40, 0x40, 0x40, 0x4C, 0x4C, 0x4C, 0x4C, 0x4C, 0x59, 0x59, 0x59, 0x59, 0x59, 0x66, 0x66, 0x66, 0x66, 0x66, 0x73, 0x73, 0x73,
0x73, 0x73, 0x80, 0x80, 0x80, 0x80, 0x80, 0x8C, 0x8C, 0x8C, 0x8C, 0x8C, 0x99, 0x99, 0x99, 0x99, 0x99, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xB3, 0xB3,
0xB3, 0xB3, 0xB3, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xE6, 0xE6, 0xE6, 0xE6, 0xE6, 0xF3,
0xF3, 0xF3, 0xF3, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x38, 0x4C, 0x41, 0x4D, 0x45, 0x34, 0x2E, 0x30, 0x20, 0x00, 0x01, 0x6E,
0x00, 0x00, 0x00, 0x00, 0x2E, 0x1E, 0x00, 0x00, 0x14, 0x40, 0x24, 0x03, 0xB0, 0x22, 0x00, 0x00, 0x40, 0x00, 0x00, 0x11, 0x23, 0xF6, 0xE8, 0xE2,
0xEB,
};
const std::uint8_t FGH_FRAME[] = {
0xFF, 0xFB, 0x90, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0x04, 0x51, 0x00, 0x06, 0xBA, 0xA8, 0x00, 0x00, 0xA0,
};
const std::uint8_t VBRI_FRAME[] = {
0xFF, 0xFB, 0x90, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x42, 0x52, 0x49, 0x00, 0x01, 0x02, 0x40, 0x00, 0x4B, 0x00, 0x00,
0x10, 0x4A, 0x00, 0x00, 0x00, 0x0A,
};
const std::uint8_t FGH_BAD_CRC_FRAME[] = {
0xFF, 0xFB, 0x90, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0x04, 0x51, 0x00, 0x06, 0xBA, 0xA8, 0x00, 0x00, 0xA1,
};
std::vector<std::uint8_t> Mp3Frame(const std::uint8_t* prefix, std::size_t prefixSize, std::size_t frameSize) {
std::vector<std::uint8_t> frame(frameSize, 0);
std::memcpy(frame.data(), prefix, prefixSize);
return frame;
}
bool OflParsesTo(const std::vector<std::uint8_t>& stream, std::uint32_t streamSize, std::uint32_t frames, std::uint32_t delay, std::uint32_t total, std::uint32_t type) {
AjmDecMp3ParseFrame frame;
std::memset(&frame, 0xFF, sizeof(frame));
if (sceAjmDecMp3ParseFrame(stream.data(), streamSize, 1, &frame) != 0) return false;
return frame.num_frames == frames && frame.encoder_delay == delay && frame.total_samples == total && frame.ofl_type == type;
}
bool OflThrows(const std::vector<std::uint8_t>& stream) {
AjmDecMp3ParseFrame frame{};
try {
sceAjmDecMp3ParseFrame(stream.data(), static_cast<std::uint32_t>(stream.size()), 1, &frame);
} catch (const std::runtime_error&) {
return true;
}
return false;
}
void TestMp3ParseOfl() {
const auto stereo = Mp3Frame(LAME_MPEG1_STEREO_VBR, sizeof(LAME_MPEG1_STEREO_VBR), 417);
Require(OflParsesTo(stereo, 417, 10, 1152 + 576 + 529, 10000, 1));
Require(ParsesTo(stereo.data(), 417, 417, 2, 1152, 128000, 44100));
Require(OflParsesTo(stereo, 48, 10, 0, 0, 0));
Require(OflParsesTo(stereo, 46, 0, 0, 0, 0));
const auto mono = Mp3Frame(LAME_MPEG1_MONO_CBR, sizeof(LAME_MPEG1_MONO_CBR), 576);
Require(OflParsesTo(mono, 576, 10, 1152 + 576 + 529, 10000, 1));
const auto mpeg2 = Mp3Frame(LAME_MPEG2_MONO_CBR, sizeof(LAME_MPEG2_MONO_CBR), 208);
Require(OflParsesTo(mpeg2, 208, 20, 576 + 576 + 529, 10000, 1));
const auto fgh = Mp3Frame(FGH_FRAME, sizeof(FGH_FRAME), 417);
Require(OflParsesTo(fgh, 417, 0, 1105, 441000, 3));
Require(OflParsesTo(Mp3Frame(FGH_BAD_CRC_FRAME, sizeof(FGH_BAD_CRC_FRAME), 417), 417, 0, 0, 0, 0));
auto vbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417);
Require(OflParsesTo(vbri, 417, 0, 576, 0, 2));
vbri.insert(vbri.end(), fgh.begin(), fgh.end());
Require(OflParsesTo(vbri, 834, 0, 576 + 1105, 441000, 4));
Require(OflParsesTo(vbri, 480, 0, 576, 0, 2));
auto protectedFrame = stereo;
protectedFrame[1] = 0xFA;
Require(OflThrows(protectedFrame));
auto layer2 = stereo;
layer2[1] = 0xFD;
Require(OflThrows(layer2));
auto monoVbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417);
monoVbri[3] = 0xC4;
Require(OflThrows(monoVbri));
}
struct GaplessDecode {
std::uint32_t totalSamples;
std::uint16_t skipSamples;
@ -339,6 +445,25 @@ void TestBatchWaitRelease(std::uint32_t context) {
Require(sceAjmBatchWait(context, second + 1, 0, &error) == invalidBatch);
}
void TestBatchCancel(std::uint32_t context) {
constexpr int invalidContext = static_cast<int>(0x80930002);
constexpr int invalidBatch = static_cast<int>(0x80930004);
std::vector<std::uint8_t> batch(64);
AjmBatchInfo info{};
AjmBatchError error{};
std::uint32_t id = 0;
Require(sceAjmBatchInitialize(batch.data(), batch.size(), &info) == 0);
Require(sceAjmBatchStart(context, &info, 0, &error, &id) == 0);
Require(sceAjmBatchCancel(context, id) == 0);
Require(sceAjmBatchCancel(context, id) == 0);
Require(sceAjmBatchCancel(0, id) == invalidContext);
Require(sceAjmBatchCancel(context + 1, id) == invalidContext);
Require(sceAjmBatchWait(context, id, 0, &error) == 0);
Require(sceAjmBatchCancel(context, id) == invalidBatch);
Require(sceAjmBatchCancel(context, 0) == invalidBatch);
Require(sceAjmBatchCancel(context, id + 1) == invalidBatch);
}
}
int main() {
@ -356,11 +481,13 @@ int main() {
Require(sceAjmDecAt9ParseConfigData(badHeader, &info) == invalidParameter);
Require(sceAjmDecAt9ParseConfigData(nullptr, &info) == invalidParameter);
TestMp3ParseFrame();
TestMp3ParseOfl();
TestMp3(context);
TestOpus(context);
TestDecodeSingle(context);
TestGaplessDecode(context);
TestCodecInfo(context);
TestBatchWaitRelease(context);
TestBatchCancel(context);
Require(sceAjmFinalize(context) == 0);
}

View File

@ -4,9 +4,12 @@
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <fstream>
#include <future>
#include <string>
#include <utility>
extern "C" {
int APS5_VABI sceAmprCommandBufferConstructor(Apr::CommandBufferObject*);
@ -33,6 +36,35 @@ int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounterOnCompletion(Apr::C
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterOnCompletion(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t);
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPairOnCompletion(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t);
int APS5_VABI sceAmprCommandBufferWriteKernelEventQueueOnCompletion(Apr::CommandBufferObject*, std::uint64_t, std::int32_t, std::uint64_t);
int APS5_VABI sceAmprCommandBufferNop(Apr::CommandBufferObject*, std::uint32_t);
int APS5_VABI sceAmprCommandBufferNopWithData(Apr::CommandBufferObject*, std::uint32_t, const std::uint32_t*);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNop(std::uint32_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeNopWithData(std::uint32_t);
int APS5_VABI sceAmprCommandBufferWaitOnAddress_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint64_t, std::uint8_t, std::uint8_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnAddress_04_00(volatile std::uint64_t*, std::uint64_t, std::uint8_t, std::uint8_t);
int APS5_VABI sceAmprCommandBufferWriteAddressFromTimeCounter_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint64_t);
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounter_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t, std::uint64_t);
int APS5_VABI sceAmprCommandBufferWriteAddressFromCounterPair_04_00(Apr::CommandBufferObject*, volatile std::uint64_t*, std::uint8_t, std::uint64_t);
int APS5_VABI sceAmprCommandBufferWriteKernelEventQueue_04_00(Apr::CommandBufferObject*, std::uint64_t, std::int32_t, std::uint64_t, std::uint64_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromTimeCounter_04_00(volatile std::uint64_t*);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounter_04_00(volatile std::uint64_t*, std::uint8_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00(volatile std::uint64_t*, std::uint8_t);
int APS5_VABI sceAmprCommandBufferWaitOnCounter_04_00(Apr::CommandBufferObject*, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t);
int APS5_VABI sceAmprCommandBufferWriteCounter_04_00(Apr::CommandBufferObject*, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWaitOnCounter_04_00(std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeWriteCounter_04_00(std::uint8_t, std::uint8_t, std::uint64_t, std::uint8_t);
int APS5_VABI sceAmprCommandBufferConstructNop(Apr::CommandBufferObject*, std::int16_t, const void*, std::uint32_t, const std::uint32_t*);
int APS5_VABI sceAmprCommandBufferConstructMarker(Apr::CommandBufferObject*, std::uint32_t, const char*, const std::uint32_t*);
int APS5_VABI sceKernelAprResolveFilepathsToIds(const char**, std::uint32_t, std::uint32_t*, std::uint32_t*);
int APS5_VABI sceAmprAprCommandBufferReadFile(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, std::uint32_t, void*, std::uint64_t, std::uint64_t);
int APS5_VABI sceAmprAprCommandBufferReadFileGather(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, std::uint64_t, std::uint64_t);
int APS5_VABI sceAmprAprCommandBufferReadFileScatter(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, void*, std::uint64_t);
int APS5_VABI sceAmprAprCommandBufferReadFileGatherScatter(Apr::CommandBufferObject*, std::uint64_t*, std::uint64_t*, void*, std::uint64_t, std::uint64_t);
int APS5_VABI sceAmprAprCommandBufferResetGatherScatterState(Apr::CommandBufferObject*);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGather(std::uint64_t, std::uint64_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileScatter(void*, std::uint64_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeReadFileGatherScatter(void*, std::uint64_t, std::uint64_t);
std::uint64_t APS5_VABI sceAmprMeasureCommandSizeResetGatherScatterState();
}
static void Require(bool value) { if (!value) std::abort(); }
@ -212,6 +244,258 @@ void TestRejectedWaitsAndCounters() {
Require(recorder.Offset() == 0 && recorder.Commands() == 0);
}
void TestNops() {
Recorder recorder;
const std::uint32_t data[3] = {0x11111111u, 0x22222222u, 0x33333333u};
for (std::uint32_t dwords = 1; dwords <= 16; ++dwords) {
const auto offset = recorder.Offset();
const auto commands = recorder.Commands();
Require(sceAmprCommandBufferNop(&recorder.buffer, dwords) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::Nop, sceAmprMeasureCommandSizeNop(dwords));
}
const auto offset = recorder.Offset();
const auto commands = recorder.Commands();
Require(sceAmprCommandBufferNopWithData(&recorder.buffer, 3, data) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::Nop, sceAmprMeasureCommandSizeNopWithData(4));
Require(std::memcmp(recorder.memory.data() + offset + sizeof(Apr::CommandHeader), data, sizeof(data)) == 0);
Require(sceAmprCommandBufferNopWithData(&recorder.buffer, 0, nullptr) == 0);
Require(sceKernelAprSubmitCommandBuffer(&recorder.buffer, 0) == 0);
const auto rejected = static_cast<std::uint64_t>(static_cast<std::uint32_t>(invalidArgument));
Require(sceAmprCommandBufferNop(&recorder.buffer, 0) == invalidArgument);
Require(sceAmprCommandBufferNop(&recorder.buffer, 17) == invalidArgument);
Require(sceAmprCommandBufferNopWithData(&recorder.buffer, 16, data) == invalidArgument);
Require(sceAmprMeasureCommandSizeNop(0) == rejected && sceAmprMeasureCommandSizeNop(17) == rejected);
Require(sceAmprMeasureCommandSizeNopWithData(0) == rejected && sceAmprMeasureCommandSizeNopWithData(17) == rejected);
}
void TestVersionedCommands() {
Recorder recorder;
alignas(8) std::uint64_t value = 0x8000000000000005ull;
alignas(8) std::uint64_t single = 0;
alignas(8) std::uint64_t pair = 0;
alignas(8) std::uint64_t time = 0;
Require(sceAmprCommandBufferWaitOnAddress_04_00(&recorder.buffer, &value, 0x8000000000000003ull, 4, 0) == 0);
Require(sceAmprCommandBufferWaitOnAddress_04_00(&recorder.buffer, &value, 1, 6, 1) == 0);
Require(sceAmprCommandBufferWaitOnAddress_04_00(&recorder.buffer, &value, 0x8000000000000000ull, 5, 0) == 0);
Require(sceAmprCommandBufferWriteCounterOnCompletion(&recorder.buffer, 10, 3) == 0);
Require(sceAmprCommandBufferWriteCounterOnCompletion(&recorder.buffer, 11, 4) == 0);
Require(sceAmprCommandBufferWriteAddressFromCounter_04_00(&recorder.buffer, &single, 10, 1) == 0);
Require(sceAmprCommandBufferWriteAddressFromCounterPair_04_00(&recorder.buffer, &pair, 10, 0) == 0);
Require(sceAmprCommandBufferWriteAddressFromTimeCounter_04_00(&recorder.buffer, &time, 1) == 0);
auto submitted = std::async(std::launch::async, [&]() { return sceKernelAprSubmitCommandBuffer(&recorder.buffer, 0); });
Require(submitted.wait_for(std::chrono::seconds(10)) == std::future_status::ready);
Require(submitted.get() == 0 && single == 3 && pair == (3ull | (4ull << 32u)) && time != 0);
const auto rejected = static_cast<std::uint64_t>(static_cast<std::uint32_t>(invalidArgument));
Recorder empty;
Require(sceAmprCommandBufferWaitOnAddress_04_00(&empty.buffer, nullptr, 0, 0, 0) == invalidArgument);
Require(sceAmprCommandBufferWaitOnAddress_04_00(&empty.buffer, &value, 0, 7, 0) == invalidArgument);
Require(sceAmprCommandBufferWaitOnAddress_04_00(&empty.buffer, &value, 0, 0, 2) == invalidArgument);
Require(sceAmprCommandBufferWriteAddressFromCounterPair_04_00(&empty.buffer, &pair, 11, 0) == invalidArgument);
Require(sceAmprCommandBufferWriteAddressFromTimeCounter_04_00(&empty.buffer, nullptr, 0) == invalidArgument);
Require(sceAmprCommandBufferWriteKernelEventQueue_04_00(&empty.buffer, 0, 1, 0, 0) == invalidArgument);
Require(empty.Offset() == 0 && empty.Commands() == 0);
Require(sceAmprMeasureCommandSizeWaitOnAddress_04_00(nullptr, 0, 0, 0) == rejected);
Require(sceAmprMeasureCommandSizeWaitOnAddress_04_00(&value, 0, 7, 0) == rejected);
Require(sceAmprMeasureCommandSizeWriteAddressFromTimeCounter_04_00(nullptr) == rejected);
Require(sceAmprMeasureCommandSizeWriteAddressFromCounter_04_00(&single, 128) == sizeof(Apr::WriteAddressFromCounterCommand));
Require(sceAmprMeasureCommandSizeWriteAddressFromCounter_04_00(nullptr, 0) == rejected);
Require(sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00(&pair, 128) == sizeof(Apr::WriteAddressFromCounterCommand));
Require(sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00(&pair, 11) == rejected);
Require(sceAmprMeasureCommandSizeWaitOnAddress_04_00(&value, 0, 6, 1) == sizeof(Apr::WaitCommand));
Require(sceAmprMeasureCommandSizeWriteAddressFromCounterPair_04_00(&pair, 10) == sizeof(Apr::WriteAddressFromCounterCommand));
}
void SubmitWithin10Seconds(const Recorder& recorder) {
auto submitted = std::async(std::launch::async, [&]() { return sceKernelAprSubmitCommandBuffer(&recorder.buffer, 0); });
Require(submitted.wait_for(std::chrono::seconds(10)) == std::future_status::ready);
Require(submitted.get() == 0);
}
void TestVersionedCounters() {
enum : std::uint8_t { size8, size4, size2Offset0, size2Offset1, size1Offset0, size1Offset1, size1Offset2, size1Offset3 };
enum : std::uint8_t { store, atomicOr, atomicAndComplement, atomicXor, atomicAdd };
Recorder recorder;
alignas(8) std::uint64_t fields = 0;
alignas(8) std::uint64_t wide = 0;
alignas(8) std::uint64_t bits = 0;
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 20, size4, 0x11223344u, store, 0) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 20, size1Offset2, 0x1AAu, store, 1) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 20, size2Offset0, 0xFFFFu, atomicAdd, 0) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 22, size8, 0x0000000500000001ull, store, 0) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 22, size8, 0xFFFFFFFFu, atomicAdd, 0) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 24, size4, 0xF0u, store, 0) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 24, size4, 0x0Fu, atomicOr, 0) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 24, size4, 0x3Cu, atomicAndComplement, 0) == 0);
Require(sceAmprCommandBufferWriteCounter_04_00(&recorder.buffer, 24, size4, 0xFFu, atomicXor, 0) == 0);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&recorder.buffer, 20, size1Offset3, 0x11u, 0, 0, 0, 0) == 0);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&recorder.buffer, 20, size1Offset2, 1u, 6, 0, 0, 1) == 0);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&recorder.buffer, 20, size2Offset0, 0xF000u, 4, 0, 0, 0) == 0);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&recorder.buffer, 20, size2Offset1, 0x11ABu, 2, 0, 0, 0) == 0);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&recorder.buffer, 22, size8, 0x0000000600000000ull, 0, 0, 0, 0) == 0);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&recorder.buffer, 24, size4, 0xFCu, 0, 1, 0x0Fu, 0) == 0);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&recorder.buffer, 24, size1Offset0, 0x3Cu, 0, 0, 0x0Fu, 0) == 0);
Require(sceAmprCommandBufferWriteAddressFromCounterOnCompletion(&recorder.buffer, &fields, 20) == 0);
Require(sceAmprCommandBufferWriteAddressFromCounterPairOnCompletion(&recorder.buffer, &wide, 22) == 0);
Require(sceAmprCommandBufferWriteAddressFromCounterOnCompletion(&recorder.buffer, &bits, 24) == 0);
Require(recorder.Commands() == 19);
SubmitWithin10Seconds(recorder);
Require(fields == 0x11AA3343u && wide == 0x0000000600000000ull && bits == 0x3Cu);
const auto rejected = static_cast<std::uint64_t>(static_cast<std::uint32_t>(invalidArgument));
Recorder empty;
Require(sceAmprCommandBufferWaitOnCounter_04_00(&empty.buffer, 0, 8, 0, 0, 0, 0, 0) == invalidArgument);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&empty.buffer, 0, size4, 0, 7, 0, 0, 0) == invalidArgument);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&empty.buffer, 0, size4, 0, 0, 2, 0, 0) == invalidArgument);
Require(sceAmprCommandBufferWaitOnCounter_04_00(&empty.buffer, 0, size4, 0, 0, 0, 0, 2) == invalidArgument);
Require(sceAmprCommandBufferWriteCounter_04_00(&empty.buffer, 128, size4, 0, store, 0) == invalidArgument);
Require(sceAmprCommandBufferWriteCounter_04_00(&empty.buffer, 0, 8, 0, store, 0) == invalidArgument);
Require(sceAmprCommandBufferWriteCounter_04_00(&empty.buffer, 0, size4, 0, 5, 0) == invalidArgument);
Require(sceAmprCommandBufferWriteCounter_04_00(nullptr, 0, size4, 0, store, 0) == invalidArgument);
Require(empty.Offset() == 0 && empty.Commands() == 0);
Require(sceAmprMeasureCommandSizeWaitOnCounter_04_00(200, size1Offset3, 0, 6, 1, 0, 1) == sizeof(Apr::WaitCommand));
Require(sceAmprMeasureCommandSizeWaitOnCounter_04_00(0, 8, 0, 0, 0, 0, 0) == rejected);
Require(sceAmprMeasureCommandSizeWaitOnCounter_04_00(0, size4, 0, 7, 0, 0, 0) == rejected);
Require(sceAmprMeasureCommandSizeWaitOnCounter_04_00(0, size4, 0, 0, 2, 0, 0) == rejected);
Require(sceAmprMeasureCommandSizeWaitOnCounter_04_00(0, size4, 0, 0, 0, 0, 2) == rejected);
Require(sceAmprMeasureCommandSizeWriteCounter_04_00(127, size8, 0, atomicAdd) == sizeof(Apr::WriteCounterCommand));
Require(sceAmprMeasureCommandSizeWriteCounter_04_00(128, size4, 0, store) == rejected);
Require(sceAmprMeasureCommandSizeWriteCounter_04_00(0, 8, 0, store) == rejected);
Require(sceAmprMeasureCommandSizeWriteCounter_04_00(0, size4, 0, 5) == rejected);
}
void TestConstructed() {
Recorder recorder;
const std::uint8_t payload[5] = {1, 2, 3, 4, 5};
const std::uint32_t word = 0xCAFEF00Du;
auto offset = recorder.Offset();
auto commands = recorder.Commands();
Require(sceAmprCommandBufferConstructNop(&recorder.buffer, 7, payload, sizeof(payload), &word) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::Nop, sceAmprMeasureCommandSizeNopWithData(4));
const std::uint8_t* data = recorder.memory.data() + offset + sizeof(Apr::CommandHeader);
const std::uint8_t padding[3] = {};
Require(std::memcmp(data, &word, sizeof(word)) == 0 && std::memcmp(data + 4, payload, sizeof(payload)) == 0 && std::memcmp(data + 9, padding, sizeof(padding)) == 0);
const std::array<std::uint8_t, 60> large{};
offset = recorder.Offset();
commands = recorder.Commands();
Require(sceAmprCommandBufferConstructNop(&recorder.buffer, 0, large.data(), 60, nullptr) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::Nop, sceAmprMeasureCommandSizeNopWithData(16));
offset = recorder.Offset();
commands = recorder.Commands();
Require(sceAmprCommandBufferConstructNop(&recorder.buffer, 0, nullptr, 0, nullptr) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::Nop, sceAmprMeasureCommandSizeNopWithData(1));
const std::pair<std::uint32_t, Apr::Opcode> markers[] = {{1, Apr::Opcode::SetMarker}, {2, Apr::Opcode::PushMarker}, {5, Apr::Opcode::SetMarker}, {6, Apr::Opcode::PushMarker}};
for (const auto& [type, opcode] : markers) {
offset = recorder.Offset();
commands = recorder.Commands();
Require(sceAmprCommandBufferConstructMarker(&recorder.buffer, type, "stream", &color) == 0);
RequireRecorded(recorder, offset, commands, opcode, sceAmprMeasureCommandSizeSetMarker("stream"), "stream");
}
offset = recorder.Offset();
commands = recorder.Commands();
Require(sceAmprCommandBufferConstructMarker(&recorder.buffer, 3, nullptr, nullptr) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::PopMarker, sceAmprMeasureCommandSizePopMarker());
Require(sceKernelAprSubmitCommandBuffer(&recorder.buffer, 0) == 0);
Recorder empty;
Require(sceAmprCommandBufferConstructNop(&empty.buffer, 0, large.data(), 61, nullptr) == invalidArgument);
Require(sceAmprCommandBufferConstructNop(&empty.buffer, 0, large.data(), 57, &word) == invalidArgument);
Require(sceAmprCommandBufferConstructNop(nullptr, 0, large.data(), 4, nullptr) == invalidArgument);
Require(sceAmprCommandBufferConstructMarker(&empty.buffer, 5, "stream", nullptr) == invalidArgument);
Require(sceAmprCommandBufferConstructMarker(&empty.buffer, 6, "stream", nullptr) == invalidArgument);
Require(sceAmprCommandBufferConstructMarker(&empty.buffer, 1, nullptr, nullptr) == invalidArgument);
Require(sceAmprCommandBufferConstructMarker(&empty.buffer, 0, "stream", &color) == invalidArgument);
Require(sceAmprCommandBufferConstructMarker(&empty.buffer, 4, "stream", &color) == invalidArgument);
Require(empty.Offset() == 0 && empty.Commands() == 0);
}
std::uint8_t FileByte(std::size_t offset) {
return static_cast<std::uint8_t>(offset * 7u + 3u);
}
bool MatchesFile(const std::uint8_t* data, std::size_t offset, std::size_t bytes) {
for (std::size_t index = 0; index < bytes; ++index) {
if (data[index] != FileByte(offset + index)) return false;
}
return true;
}
void TestGatherScatter() {
const char* path = "ampr_gather_scatter.bin";
{
std::ofstream file(path, std::ios::binary);
for (std::size_t offset = 0; offset < 4096; ++offset) file.put(static_cast<char>(FileByte(offset)));
}
std::uint32_t fileId = 0;
std::uint32_t failed = 0;
Require(sceKernelAprResolveFilepathsToIds(&path, 1, &fileId, &failed) == 0);
Recorder recorder;
auto* buffer = &recorder.buffer;
auto* map = &recorder.gatherState;
auto* state = &recorder.scatterState;
std::array<std::uint8_t, 64> first{};
std::array<std::uint8_t, 64> second{};
Require(sceAmprAprCommandBufferReadFileGather(buffer, map, state, 8, 0) == invalidArgument);
Require(sceAmprAprCommandBufferReadFileScatter(buffer, map, state, second.data(), 8) == invalidArgument);
Require(sceAmprAprCommandBufferReadFileGatherScatter(buffer, map, state, second.data(), 8, 0) == invalidArgument);
Require(recorder.Commands() == 0);
Require(sceAmprAprCommandBufferReadFile(buffer, map, state, fileId, first.data(), 16, 100) == 0);
auto offset = recorder.Offset();
auto commands = recorder.Commands();
Require(sceAmprAprCommandBufferReadFileGather(buffer, map, state, 8, 500) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::ReadFileGather, sceAmprMeasureCommandSizeReadFileGather(8, 500));
offset = recorder.Offset();
commands = recorder.Commands();
Require(sceAmprAprCommandBufferReadFileScatter(buffer, map, state, second.data(), 8) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::ReadFileScatter, sceAmprMeasureCommandSizeReadFileScatter(second.data(), 8));
offset = recorder.Offset();
commands = recorder.Commands();
Require(sceAmprAprCommandBufferReadFileGatherScatter(buffer, map, state, second.data() + 32, 4, 1000) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::ReadFileGatherScatter, sceAmprMeasureCommandSizeReadFileGatherScatter(second.data() + 32, 4, 1000));
Require(sceAmprAprCommandBufferReadFileGather(buffer, map, state, 4, 2000) == 0);
Require(sceAmprAprCommandBufferReadFileScatter(buffer, map, state, first.data() + 40, 6) == 0);
offset = recorder.Offset();
commands = recorder.Commands();
Require(sceAmprAprCommandBufferResetGatherScatterState(buffer) == 0);
RequireAppended(recorder, offset, commands, Apr::Opcode::ResetGatherScatterState, sceAmprMeasureCommandSizeResetGatherScatterState());
Require(sceAmprAprCommandBufferReadFileGather(buffer, map, state, 8, 0) == invalidArgument);
Require(sceAmprAprCommandBufferReadFileScatter(buffer, map, state, second.data(), 8) == invalidArgument);
Require(sceAmprAprCommandBufferReadFile(buffer, map, state, fileId, second.data() + 48, 4, 3000) == 0);
Require(sceAmprAprCommandBufferReadFileScatter(buffer, map, state, second.data() + 56, 4) == 0);
Require(recorder.Commands() == 9);
SubmitWithin10Seconds(recorder);
Require(MatchesFile(first.data(), 100, 16) && MatchesFile(first.data() + 16, 500, 8));
Require(MatchesFile(second.data(), 508, 8));
Require(MatchesFile(second.data() + 32, 1000, 4) && MatchesFile(second.data() + 36, 2000, 4));
Require(MatchesFile(first.data() + 40, 2004, 6));
Require(MatchesFile(second.data() + 48, 3000, 4) && MatchesFile(second.data() + 56, 3004, 4));
Require(first[24] == 0 && first[39] == 0 && second[8] == 0 && second[31] == 0 && second[40] == 0);
std::remove(path);
const auto rejected = static_cast<std::uint64_t>(static_cast<std::uint32_t>(invalidArgument));
auto* high = reinterpret_cast<void*>(std::uintptr_t{0xF00000000000ull});
Recorder empty;
Require(sceAmprAprCommandBufferReadFile(&empty.buffer, map, state, fileId, first.data(), 0, 0) == invalidArgument);
Require(sceAmprAprCommandBufferReadFile(&empty.buffer, map, state, fileId, first.data(), 0x100000001ull, 0) == invalidArgument);
Require(sceAmprAprCommandBufferReadFile(&empty.buffer, map, state, fileId, first.data(), 4, 0x10000000000ull) == invalidArgument);
Require(sceAmprAprCommandBufferReadFile(&empty.buffer, map, state, fileId, high, 4, 0) == invalidArgument);
Require(sceAmprAprCommandBufferReadFile(nullptr, map, state, fileId, first.data(), 4, 0) == invalidArgument);
Require(empty.Offset() == 0 && empty.Commands() == 0);
Require(sceAmprMeasureCommandSizeReadFileGather(0, 0) == rejected);
Require(sceAmprMeasureCommandSizeReadFileGather(0x100000000ull, 0x10000000000ull - 1u) == sizeof(Apr::ReadFileCommand));
Require(sceAmprMeasureCommandSizeReadFileGather(4, 0x10000000000ull) == rejected);
Require(sceAmprMeasureCommandSizeReadFileScatter(first.data(), 0x100000001ull) == rejected);
Require(sceAmprMeasureCommandSizeReadFileScatter(high, 4) == rejected);
Require(sceAmprMeasureCommandSizeReadFileGatherScatter(first.data(), 4, 0x10000000000ull) == rejected);
Require(sceAmprMeasureCommandSizeReadFileGatherScatter(nullptr, 4, 0) == sizeof(Apr::ReadFileCommand));
}
}
int main() {
@ -226,5 +510,10 @@ int main() {
TestWaits();
TestCounters();
TestRejectedWaitsAndCounters();
TestNops();
TestVersionedCommands();
TestVersionedCounters();
TestConstructed();
TestGatherScatter();
return 0;
}

View File

@ -0,0 +1,187 @@
#include "SceTypes.hpp"
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <thread>
extern "C" {
void APS5_VABI sceAudio3dGetDefaultOpenParameters(Audio3dOpenParameters* parameters);
int APS5_VABI sceAudio3dInitialize(std::int64_t reserved);
int APS5_VABI sceAudio3dPortOpen(int user_id, const Audio3dOpenParameters* parameters, std::uint32_t* id);
int APS5_VABI sceAudio3dPortClose(std::uint32_t port_id);
int APS5_VABI sceAudio3dPortSetAttribute(std::uint32_t port_id, std::uint32_t attribute_id, const void* attribute, std::size_t attribute_size);
int APS5_VABI sceAudio3dPortGetQueueLevel(std::uint32_t port_id, std::uint32_t* queue_level, std::uint32_t* queue_available);
int APS5_VABI sceAudio3dPortAdvance(std::uint32_t port_id);
int APS5_VABI sceAudio3dPortPush(std::uint32_t port_id, std::uint32_t blocking);
}
namespace {
constexpr int INVALID_PORT = static_cast<int>(0x80EA0002);
constexpr int INVALID_PARAMETER = static_cast<int>(0x80EA0004);
constexpr int OUT_OF_RESOURCES = static_cast<int>(0x80EA0006);
constexpr int NOT_READY = static_cast<int>(0x80EA0007);
constexpr int SYSTEM_USER = 0xFF;
void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "%s\n", message);
std::abort();
}
template <typename F>
void RequireThrows(F f, const char* message) {
bool threw = false;
try {
f();
} catch (const std::exception&) {
threw = true;
}
Require(threw, message);
}
Audio3dOpenParameters Defaults() {
Audio3dOpenParameters parameters{};
sceAudio3dGetDefaultOpenParameters(&parameters);
return parameters;
}
int Open(const Audio3dOpenParameters& parameters, std::uint32_t* id) {
return sceAudio3dPortOpen(SYSTEM_USER, &parameters, id);
}
void RequireLevel(std::uint32_t level, std::uint32_t available, const char* message) {
std::uint32_t got_level = 99;
std::uint32_t got_available = 99;
Require(sceAudio3dPortGetQueueLevel(0, &got_level, &got_available) == 0, message);
Require(got_level == level && got_available == available, message);
}
void CheckBeforeInitialize() {
std::uint32_t id = 7;
Audio3dOpenParameters parameters = Defaults();
Require(sceAudio3dInitialize(1) == INVALID_PARAMETER, "non-zero reserved must be rejected");
Require(Open(parameters, &id) == NOT_READY, "open before initialize must be NOT_READY");
Require(id == 7, "a failed open must not write the port id");
Require(sceAudio3dPortOpen(1, nullptr, nullptr) == NOT_READY, "NOT_READY is checked before the arguments");
std::uint32_t level = 0;
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == INVALID_PORT, "queue level needs an open port");
Require(sceAudio3dPortClose(0) == INVALID_PORT, "close needs an open port");
Require(sceAudio3dPortAdvance(0) == INVALID_PORT, "advance needs an open port");
Require(sceAudio3dPortPush(0, 0) == INVALID_PORT, "push needs an open port");
float value = 0.0f;
Require(sceAudio3dPortSetAttribute(0, 0x10001, &value, sizeof(value)) == INVALID_PORT, "set attribute needs an open port");
Require(sceAudio3dInitialize(0) == 0, "initialize");
RequireThrows([] { sceAudio3dInitialize(0); }, "repeated initialize must throw");
}
void CheckOpenParameters() {
std::uint32_t id = 7;
const Audio3dOpenParameters defaults = Defaults();
Require(sceAudio3dPortOpen(1, &defaults, &id) == INVALID_PARAMETER, "user id must be the system user");
Require(sceAudio3dPortOpen(SYSTEM_USER, nullptr, &id) == INVALID_PARAMETER, "null parameters");
Require(sceAudio3dPortOpen(SYSTEM_USER, &defaults, nullptr) == INVALID_PARAMETER, "null port id");
auto rejects = [&](auto change, const char* message) {
Audio3dOpenParameters parameters = defaults;
change(parameters);
Require(Open(parameters, &id) == INVALID_PARAMETER, message);
Require(id == 7, "a failed open must not write the port id");
};
rejects([](auto& p) { p.size_this = 0x30; }, "unknown parameter size");
rejects([](auto& p) { p.size_this = 0x21; }, "parameter size is not masked");
rejects([](auto& p) { p.rate = 1; }, "only 48 kHz");
rejects([](auto& p) { p.granularity = 0x80; }, "granularity below 256");
rejects([](auto& p) { p.granularity = 0x180; }, "granularity not a multiple of 256");
rejects([](auto& p) { p.max_objects = 0; }, "zero objects");
rejects([](auto& p) { p.queue_depth = 0; }, "zero queue depth");
rejects([](auto& p) { p.buffer_mode = 3; }, "buffer mode above 2");
rejects([](auto& p) { p.size_this = 0x28; p.num_beds = 4; }, "4 beds");
rejects([](auto& p) { p.size_this = 0x28; p.num_beds = 1; }, "1 bed");
auto throws = [&](auto change, const char* message) {
Audio3dOpenParameters parameters = defaults;
change(parameters);
RequireThrows([&] { Open(parameters, &id); }, message);
};
throws([](auto& p) { p.size_this = 0x10; }, "0x10 parameters select buffer mode 0");
throws([](auto& p) { p.size_this = 0x18; }, "0x18 parameters select buffer mode 1");
throws([](auto& p) { p.buffer_mode = 1; }, "buffer mode 1");
throws([](auto& p) { p.size_this = 0x28; p.num_beds = 3; }, "3 beds");
}
void CheckOpenClose() {
std::uint32_t id = 7;
Audio3dOpenParameters parameters = Defaults();
parameters.size_this = 0x28;
parameters.num_beds = 2;
Require(Open(parameters, &id) == 0 && id == 0, "open hands out port 0");
std::uint32_t second = 7;
Require(Open(parameters, &second) == OUT_OF_RESOURCES && second == 7, "only one port");
std::uint32_t level = 0;
Require(sceAudio3dPortGetQueueLevel(1, &level, nullptr) == INVALID_PORT, "port 1 does not exist");
Require(sceAudio3dPortClose(1) == INVALID_PORT, "close of port 1");
Require(sceAudio3dPortClose(0) == 0, "close");
Require(sceAudio3dPortClose(0) == INVALID_PORT, "double close");
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == INVALID_PORT, "closed port");
}
void CheckAttributes() {
float value = 0.5f;
int flag = 1;
Require(sceAudio3dPortSetAttribute(0, 0x10001, nullptr, 4) == INVALID_PARAMETER, "null attribute");
Require(sceAudio3dPortSetAttribute(1, 0x10001, &value, 4) == INVALID_PORT, "attribute on port 1");
Require(sceAudio3dPortSetAttribute(0, 0x10001, &value, 4) == 0, "late reverb level");
Require(sceAudio3dPortSetAttribute(0, 0x10002, &value, 4) == 0, "downmix spread radius");
Require(sceAudio3dPortSetAttribute(0, 0x10003, &flag, 4) == 0, "downmix spread height aware");
RequireThrows([&] { sceAudio3dPortSetAttribute(0, 0x10004, &value, 4); }, "unknown attribute must throw");
RequireThrows([&] { sceAudio3dPortSetAttribute(0, 0x10001, &value, 8); }, "wrong attribute size must throw");
}
void CheckQueue() {
using namespace std::chrono;
constexpr std::uint32_t granularity = 0x1800;
constexpr auto frame = microseconds(1000000ull * granularity / 48000);
Audio3dOpenParameters parameters = Defaults();
parameters.granularity = granularity;
std::uint32_t id = 7;
Require(Open(parameters, &id) == 0 && id == 0, "open");
CheckAttributes();
Require(sceAudio3dPortGetQueueLevel(0, nullptr, nullptr) == INVALID_PARAMETER, "both outputs null");
std::uint32_t available = 0;
Require(sceAudio3dPortGetQueueLevel(0, nullptr, &available) == 0 && available == 2, "level pointer is optional");
RequireLevel(0, 2, "empty queue");
Require(sceAudio3dPortPush(0, 1) == 0, "push of an empty queue");
RequireLevel(0, 2, "empty push queues nothing");
Require(sceAudio3dPortAdvance(0) == 0, "advance 1");
RequireLevel(1, 1, "one frame queued");
Require(sceAudio3dPortAdvance(0) == 0, "advance 2");
RequireLevel(2, 0, "queue full");
RequireThrows([] { sceAudio3dPortAdvance(0); }, "advance into a full queue must throw");
RequireThrows([] { sceAudio3dPortPush(0, 2); }, "unknown blocking mode must throw");
auto start = steady_clock::now();
Require(sceAudio3dPortPush(0, 1) == 0, "blocking push");
auto waited = steady_clock::now() - start;
Require(waited >= frame - milliseconds(2), "blocking push waits until one frame has played");
RequireLevel(1, 1, "blocking push returns with one free slot");
Require(sceAudio3dPortAdvance(0) == 0, "advance 3");
start = steady_clock::now();
Require(sceAudio3dPortPush(0, 0) == 0, "async push");
Require(steady_clock::now() - start < frame, "async push does not wait for a frame");
std::uint32_t level = 0;
Require(sceAudio3dPortGetQueueLevel(0, &level, nullptr) == 0 && level >= 1, "async push keeps frames queued");
std::this_thread::sleep_for(frame * 3);
RequireLevel(0, 2, "frames drain at the port's rate");
Require(sceAudio3dPortClose(0) == 0, "close");
}
}
int main() {
CheckBeforeInitialize();
CheckOpenParameters();
CheckOpenClose();
CheckQueue();
}

View File

@ -1,5 +1,7 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <thread>
extern "C" {
void* APS5_VABI dlopen_nid_postfix(const char*, int);
@ -37,4 +39,20 @@ int main(int argc, char** argv) {
Require(add && add(2, 3) == 5);
Require(dlclose_nid_postfix(second) == 0);
Require(dlclose_nid_postfix(second) == -1);
const std::filesystem::path guest = "anyps5-relinked-module-for-test.prx";
auto relinked = guest;
relinked += ".guest.prx";
{
std::ofstream elf(guest, std::ios::binary);
elf.write("\x7f" "ELF", 4);
}
std::filesystem::copy_file(argv[1], relinked, std::filesystem::copy_options::overwrite_existing);
void* redirected = dlopen_nid_postfix(guest.string().c_str(), 2);
Require(redirected != nullptr);
add = reinterpret_cast<Add>(dlsym_nid_postfix(redirected, "GuestModuleAdd"));
Require(add && add(40, 2) == 42);
Require(dlclose_nid_postfix(redirected) == 0);
std::filesystem::remove(guest);
std::filesystem::remove(relinked);
}

View File

@ -0,0 +1,70 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <atomic>
#include <cstdlib>
#include <thread>
using Callback = int (APS5_VABI *)(void*, void*, void**);
extern "C" int APS5_VABI _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(int*, Callback, void*);
static int ExecuteOnce(int* flag, Callback callback, void* arg) {
return _ZSt13_Execute_onceRSt9once_flagPFiPvS1_PS1_ES1__nid_postfix(flag, callback, arg);
}
static void Require(bool value) { if (!value) std::abort(); }
static std::atomic<int> calls{0}, arrived{0};
static int control = 0, nested = 0, failing = 0, throwing = 0;
static int published = 0, nestedValue = 0, failures = 0, throws = 0;
static void* seenFirst = &control;
static void* seenArg = nullptr;
static void** seenThird = reinterpret_cast<void**>(&control);
static int APS5_VABI Nested(void*, void* arg, void**) {
nestedValue = *static_cast<int*>(arg);
return 1;
}
static int APS5_VABI Initialize(void* first, void* arg, void** third) {
++calls;
seenFirst = first;
seenArg = arg;
seenThird = third;
while (arrived.load() != 16) std::this_thread::yield();
int value = 17;
Require(ExecuteOnce(&nested, Nested, &value) == 1);
published = 42;
return 5;
}
static int APS5_VABI Fail(void*, void*, void**) {
++failures;
return 0;
}
static int APS5_VABI Throw(void*, void*, void**) {
if (++throws == 1) throw 7;
return 1;
}
static int APS5_VABI Unreachable(void*, void*, void**) {
std::abort();
}
int main() {
int token = 0;
std::array<std::thread, 16> workers;
for (auto& worker : workers) worker = std::thread([&token] {
++arrived;
Require(ExecuteOnce(&control, Initialize, &token) == 1);
Require(published == 42 && nestedValue == 17);
});
for (auto& worker : workers) worker.join();
Require(calls == 1 && control == 1 && nested == 1);
Require(seenFirst == nullptr && seenArg == &token && seenThird == nullptr);
Require(ExecuteOnce(&control, Unreachable, nullptr) == 1 && calls == 1);
Require(ExecuteOnce(&failing, Fail, nullptr) == 0 && failing == 0 && failures == 1);
Require(ExecuteOnce(&failing, Fail, nullptr) == 0 && failing == 0 && failures == 2);
try { ExecuteOnce(&throwing, Throw, nullptr); std::abort(); }
catch (int value) { Require(value == 7 && throwing == 0); }
Require(ExecuteOnce(&throwing, Throw, nullptr) == 1 && throwing == 1 && throws == 2);
int preset = -3;
Require(ExecuteOnce(&preset, Unreachable, nullptr) == 1 && preset == -3);
}

View File

@ -8,6 +8,8 @@ int APS5_VABI clock_getres_nid_postfix(int clockId, KernelTimespec* res);
int APS5_VABI sceKernelClockGettime(KernelClockid clockId, KernelTimespec* tp);
int APS5_VABI sceKernelClockGetres(KernelClockid clockId, KernelTimespec* tp);
int APS5_VABI sceKernelUsleep_nid_postfix(KernelUseconds microseconds);
int APS5_VABI gettimeofday_nid_postfix(KernelTimeval* tv, KernelTimezone* tz);
std::int64_t APS5_VABI _Xtime_get_ticks_nid_postfix();
}
static constexpr int SCE_OK = 0;
@ -120,6 +122,21 @@ static void IdleProcessAccumulatesNone() {
Require(Nanos(GUEST_CLOCK_PROF) - profStart <= IDLE_LIMIT_NANOS);
}
static std::int64_t Microseconds() {
KernelTimeval time{-1, -1};
Require(gettimeofday_nid_postfix(&time, nullptr) == SCE_OK);
return time.tv_sec * 1000000LL + time.tv_usec;
}
static void XtimeTicksAreGettimeofdayMicroseconds() {
for (int i = 0; i < 100; ++i) {
const std::int64_t before = Microseconds();
const std::int64_t ticks = _Xtime_get_ticks_nid_postfix();
const std::int64_t after = Microseconds();
Require(before <= ticks && ticks <= after);
}
}
int main() {
SecondClockReportsWholeSeconds();
@ -135,4 +152,5 @@ int main() {
ProcessClocksAreOrdered();
BusyProcessAccumulatesUserTime();
IdleProcessAccumulatesNone();
XtimeTicksAreGettimeofdayMicroseconds();
}

View File

@ -0,0 +1,109 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <string_view>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
extern "C" int APS5_VABI sceHttpParseResponseHeader(const char*, std::size_t, const char*, const char**, std::size_t*);
static void Require(bool value) {
if (!value) {
std::fputs("HTTP response header check failed\n", stderr);
std::abort();
}
}
static void Check(std::string_view header, const char* field, std::size_t offset, std::string_view expected, int consumed) {
const char* value = nullptr;
std::size_t length = 999;
const int result = sceHttpParseResponseHeader(header.data(), header.size(), field, &value, &length);
if (result != consumed) {
std::fprintf(stderr, "Field %s: consumed %d bytes, expected %d\n", field, result, consumed);
}
Require(result == consumed);
Require(length == expected.size());
Require(value == (expected.empty() ? nullptr : header.data() + offset));
if (length != 0) Require(std::memcmp(value, expected.data(), length) == 0);
}
static void CheckGuardPage() {
#ifdef _WIN32
SYSTEM_INFO info;
GetSystemInfo(&info);
const std::size_t pageSize = info.dwPageSize;
auto* pages = static_cast<char*>(VirtualAlloc(nullptr, 2 * pageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
Require(pages != nullptr);
DWORD oldProtection = 0;
Require(VirtualProtect(pages + pageSize, pageSize, PAGE_NOACCESS, &oldProtection) != 0);
#else
const long hostPageSize = sysconf(_SC_PAGESIZE);
Require(hostPageSize > 0);
const std::size_t pageSize = static_cast<std::size_t>(hostPageSize);
auto* pages = static_cast<char*>(mmap(nullptr, 2 * pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
Require(pages != MAP_FAILED);
Require(mprotect(pages + pageSize, pageSize, PROT_NONE) == 0);
#endif
constexpr std::string_view text = "X: abc\r\n\tdef\r\n";
char* header = pages + pageSize - text.size();
std::memcpy(header, text.data(), text.size());
Check({header, text.size()}, "x", 3, "abc\r\n\tdef", static_cast<int>(text.size()));
Check({header, 6}, "x", 3, "abc", 6);
const char* value = header;
std::size_t length = 123;
Require(sceHttpParseResponseHeader(header, text.size(), "not-present", &value, &length) == static_cast<int>(0x80432025));
Require(value == header && length == 123);
Require(sceHttpParseResponseHeader(pages + pageSize, 0, "x", &value, &length) == static_cast<int>(0x80432025));
pages[pageSize - 1] = 'X';
Require(sceHttpParseResponseHeader(pages + pageSize - 1, 1, "x", &value, &length) == static_cast<int>(0x80432025));
#ifdef _WIN32
Require(VirtualFree(pages, 0, MEM_RELEASE) != 0);
#else
Require(munmap(pages, 2 * pageSize) == 0);
#endif
}
int main() {
constexpr int invalidResponse = static_cast<int>(0x80432060);
constexpr int invalidValue = static_cast<int>(0x804321FE);
constexpr int notFound = static_cast<int>(0x80432025);
Check("HTTP/1.1 200 OK\r\nX: \tvalue \t\r\nNext: no\r\n", "x", 21, "value \t", 30);
Check("X: first\r\nX: second\r\n", "X", 3, "first", 10);
Check("X: abc\n def\n\tghi\nY: no\n", "x", 3, "abc\n def\n\tghi", 17);
Check("X: no-newline", "X", 3, "no-newline", 13);
Check("X: value\r", "X", 3, "value\r", 9);
Check("X:", "X", 2, "", 0);
Check("X: \t", "X", 4, "", 0);
Check("X:\r\n\r\n", "X", 5, "", 0);
Check("X:\r\nnext\r\n", "X", 4, "next", 10);
Check("Prefix: wrong\n X: wrong\nXy: wrong\nX: right\n", "X", 37, "right", 43);
Check(std::string_view("X: a\0b\n", 7), "X", 3, std::string_view("a\0b", 3), 7);
const char* marker = "unchanged";
const char* value = marker;
std::size_t length = 456;
Require(sceHttpParseResponseHeader(nullptr, 1, "X", &value, &length) == invalidResponse);
Require(sceHttpParseResponseHeader(nullptr, 0, nullptr, nullptr, nullptr) == invalidResponse);
Require(sceHttpParseResponseHeader("X: yes", 6, nullptr, &value, &length) == invalidValue);
Require(sceHttpParseResponseHeader("X: yes", 6, "X", nullptr, &length) == invalidValue);
Require(sceHttpParseResponseHeader("X: yes", 6, "X", &value, nullptr) == invalidValue);
Require(sceHttpParseResponseHeader("X: yes", 0, "X", &value, &length) == notFound);
Require(sceHttpParseResponseHeader("X: yes", 1, "X", &value, &length) == notFound);
Require(sceHttpParseResponseHeader("X: yes", 6, "Longer-Field", &value, &length) == notFound);
Require(sceHttpParseResponseHeader("X: yes", 6, "X:", &value, &length) == notFound);
Require(value == marker && length == 456);
std::string field(0xfff, 'A');
std::string header = field + ": value\n";
field += "ignored";
Check(header, field.c_str(), 0x1000 + 1, "value", static_cast<int>(header.size()));
CheckGuardPage();
}

View File

@ -0,0 +1,67 @@
#include "SceTypes.hpp"
#include <cstdio>
#include <cstdlib>
#include <cstring>
extern "C" {
int APS5_VABI sceImeKeyboardOpen(int32_t user_id, const KeyboardParam* param);
int APS5_VABI sceImeKeyboardClose(int32_t user_id);
int APS5_VABI sceImeKeyboardGetInfo(uint32_t resource_id, KeyboardInfo* info);
int APS5_VABI sceImeKeyboardSetMode(int32_t user_id, uint32_t mode);
}
static_assert(sizeof(KeyboardInfo) == 36);
constexpr int NotOpened = static_cast<int>(0x80bc0002u);
constexpr int InvalidUserId = static_cast<int>(0x80bc0010u);
constexpr int NoResourceId = static_cast<int>(0x80bc0023u);
constexpr int InvalidMode = static_cast<int>(0x80bc0024u);
constexpr int InvalidAddress = static_cast<int>(0x80bc0031u);
static void Require(bool value, const char* message) {
if (value) return;
std::fprintf(stderr, "%s\n", message);
std::abort();
}
static void RequireUntouched(const KeyboardInfo& info) {
const auto* bytes = reinterpret_cast<const unsigned char*>(&info);
for (size_t i = 0; i < sizeof(info); ++i) Require(bytes[i] == 0xa5, "keyboard info written");
}
static void CheckClosed() {
KeyboardInfo info;
std::memset(&info, 0xa5, sizeof(info));
Require(sceImeKeyboardGetInfo(0, nullptr) == InvalidAddress, "null info");
Require(sceImeKeyboardGetInfo(0, &info) == NotOpened, "info without an open keyboard");
RequireUntouched(info);
Require(sceImeKeyboardSetMode(-1, 0) == InvalidUserId, "set mode invalid user");
Require(sceImeKeyboardSetMode(1, 0) == NotOpened, "set mode without an open keyboard");
Require(sceImeKeyboardSetMode(1, 0x80) == NotOpened, "set mode checks the mode before the keyboard");
}
static void CheckOpened() {
KeyboardParam param{};
Require(sceImeKeyboardOpen(1, &param) == 0, "keyboard open failed");
KeyboardInfo info;
std::memset(&info, 0xa5, sizeof(info));
Require(sceImeKeyboardGetInfo(0, nullptr) == InvalidAddress, "null info with an open keyboard");
Require(sceImeKeyboardGetInfo(0, &info) == NoResourceId, "resource id 0 reported as a keyboard");
Require(sceImeKeyboardGetInfo(0x12345678, &info) == NoResourceId, "unknown resource id reported as a keyboard");
RequireUntouched(info);
Require(sceImeKeyboardSetMode(-1, 0) == InvalidUserId, "set mode invalid user with an open keyboard");
Require(sceImeKeyboardSetMode(2, 0) == NotOpened, "set mode for a user without an open keyboard");
Require(sceImeKeyboardSetMode(1, 0) == 0, "mode 0 rejected");
Require(sceImeKeyboardSetMode(1, 0x7f) == 0, "all mode bits rejected");
Require(sceImeKeyboardSetMode(1, 0x41) == 0, "manual mode without format characters rejected");
Require(sceImeKeyboardSetMode(1, 0x80) == InvalidMode, "mode bit 7 accepted");
Require(sceImeKeyboardSetMode(1, 0x80000001u) == InvalidMode, "mode bit 31 accepted");
Require(sceImeKeyboardClose(1) == 0, "keyboard close failed");
Require(sceImeKeyboardGetInfo(0, &info) == NotOpened, "info after close");
Require(sceImeKeyboardSetMode(1, 0) == NotOpened, "set mode after close");
}
int main() {
CheckClosed();
CheckOpened();
}

View File

@ -10,6 +10,9 @@ int APS5_VABI sceMsgDialogUpdateStatus(void);
int APS5_VABI sceMsgDialogGetResult(MsgDialogResult* result);
int APS5_VABI sceMsgDialogClose(void);
int APS5_VABI sceMsgDialogTerminate(void);
int APS5_VABI sceMsgDialogProgressBarInc(int target, std::uint32_t delta);
int APS5_VABI sceMsgDialogProgressBarSetMsg(int target, const char* msg);
int APS5_VABI sceMsgDialogProgressBarSetValue(int target, std::uint32_t rate);
}
namespace {
@ -33,8 +36,12 @@ int main() {
Require(sceMsgDialogClose() == kErrNotRunning);
Require(sceMsgDialogUpdateStatus() == kStatusInitialized);
Require(sceMsgDialogProgressBarSetValue(0, 50) == kErrNotRunning);
Require(sceMsgDialogOpen(param) == 0);
Require(sceMsgDialogUpdateStatus() == kStatusFinished);
Require(sceMsgDialogProgressBarInc(0, 10) == kErrNotRunning);
Require(sceMsgDialogProgressBarSetMsg(0, "progress") == kErrNotRunning);
Require(sceMsgDialogProgressBarSetValue(0, 100) == kErrNotRunning);
Require(sceMsgDialogClose() == kErrNotRunning);
Require(sceMsgDialogUpdateStatus() == kStatusFinished);

View File

@ -1,4 +1,5 @@
#include "prx/libScePad/include/Pad.hpp"
#include "prx/libScePad/include/PadInputTypes.hpp"
#include "prx/libScePad/include/PadState.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <chrono>
@ -42,6 +43,20 @@ static void CheckTiltCorrection(int handle) {
PadPublishInput_nid_postfix(PadInputState{});
}
static void CheckTouchContact() {
PadInputState touch;
touch.buttons = static_cast<std::uint32_t>(Pad::PadButton::TouchPad);
touch.touch[0] = {true, 960, 471, 0};
PadPublishInput_nid_postfix(touch);
const auto data = Pad::ReadState();
Require((data.buttons & static_cast<std::uint32_t>(Pad::PadButton::TouchPad)) != 0);
Require(data.touch_data_touch_num == 1);
Require(data.touch_data_touch0_x == 960);
Require(data.touch_data_touch0_y == 471);
PadPublishInput_nid_postfix(PadInputState{});
Pad::ReadState();
}
int main() {
constexpr int noHandle = static_cast<int>(0x80920008);
constexpr int user = 0x10000000;
@ -55,6 +70,7 @@ int main() {
Require(scePadGetHandle(user, 0, 0) == handle);
Require(scePadGetHandle(user, 2, 0) == handle);
CheckTiltCorrection(handle);
CheckTouchContact();
Require(scePadGetHandle(0xff, 16, 0) == handle);
Require(scePadGetHandle(user, 16, 0) == noHandle);
Require(scePadGetHandle(user, 0, 1) == noHandle);

View File

@ -1,9 +1,11 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "SceTypes.hpp"
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <thread>
extern "C" {
int APS5_VABI sceNetInit_nid_postfix(void);
@ -18,6 +20,8 @@ std::int64_t APS5_VABI sceNetRecv(int, void*, std::size_t, int);
std::int64_t APS5_VABI sceNetSendto(int, const void*, std::size_t, int, const void*, std::uint32_t);
std::int64_t APS5_VABI sceNetRecvfrom(int, void*, std::size_t, int, void*, std::uint32_t*);
int APS5_VABI sceNetSocketClose(int);
int APS5_VABI sceNetSetsockopt(int, int, int, const void*, std::uint32_t);
int* APS5_VABI sceNetErrnoLoc(void);
int APS5_VABI sceNetEpollCreate(const char*, int);
int APS5_VABI sceNetEpollControl(int, int, int, const NetEpollEvent*);
int APS5_VABI sceNetEpollWait(int, NetEpollEvent*, int, int);
@ -53,6 +57,10 @@ int main() {
address_size = peer.size();
const int accepted = sceNetAccept(listener, peer.data(), &address_size);
Require(accepted >= 0 && address_size == 16);
const int nonblocking = 1;
Require(sceNetSetsockopt(accepted, 0xffff, 0x1200, &nonblocking, sizeof(nonblocking)) == 0);
char pending = 0;
Require(sceNetRecv(accepted, &pending, sizeof(pending), 0) == static_cast<int>(0x80410123) && *sceNetErrnoLoc() == 35);
const int epoll = sceNetEpollCreate("guest-sce-net", 0);
Require(epoll >= 0);
@ -71,8 +79,15 @@ int main() {
Require(std::strcmp(request, response) == 0);
Require(sceNetEpollDestroy(epoll) == 0);
Require(sceNetSocketClose(accepted) == 0);
bool send_failed = false;
for (int attempt = 0; attempt < 100 && !send_failed; ++attempt) {
send_failed = sceNetSend(client, request, sizeof(request), 0) < 0;
if (!send_failed) std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
Require(send_failed);
Require(sceNetSocketClose(client) == 0);
Require(sceNetSocketClose(listener) == 0);
Require(sceNetSocketClose(listener) == static_cast<int>(0x80410109) && *sceNetErrnoLoc() == 9);
const int udp_receiver = sceNetSocket(nullptr, 2, 2, 17);
const int udp_sender = sceNetSocket(nullptr, 2, 2, 17);

View File

@ -1,9 +1,15 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <initializer_list>
extern "C" {
int APS5_VABI sceShareCaptureScreenshotExtended(const void* extended_param, std::int32_t* req_id);
int APS5_VABI sceShareCaptureScreenshot(const void* param, std::int32_t* req_id);
int APS5_VABI sceShareCaptureVideoClip(const void* param, std::int32_t* req_id);
int APS5_VABI sceShareGetCurrentStatus(std::uint32_t feature_flag, void* status);
int APS5_VABI sceShareOpenMenuForContent(const void* content_id);
}
namespace {
@ -26,5 +32,32 @@ int main() {
Require(sceShareCaptureScreenshotExtended(param, nullptr) == notSupported);
Require(sceShareCaptureScreenshotExtended(nullptr, nullptr) == notSupported);
using Capture = int (APS5_VABI*)(const void*, std::int32_t*);
for (Capture capture : {sceShareCaptureScreenshot, sceShareCaptureVideoClip}) {
reqId = 7;
Require(capture(param, &reqId) == notSupported);
Require(reqId == -1);
reqId = 7;
Require(capture(nullptr, &reqId) == notSupported);
Require(reqId == -1);
Require(capture(param, nullptr) == notSupported);
}
Require(sceShareOpenMenuForContent(param) == notSupported);
Require(sceShareOpenMenuForContent(nullptr) == notSupported);
constexpr std::int32_t invalidParam = static_cast<std::int32_t>(0x81960002);
std::uint8_t status[18];
std::memset(status, 0x5a, sizeof(status));
Require(sceShareGetCurrentStatus(1, status) == 0);
for (std::size_t index = 0; index < sizeof(status); ++index) Require(status[index] == (index < 16 ? 0 : 0x5a));
std::memset(status, 0x5a, sizeof(status));
Require(sceShareGetCurrentStatus(0xffffffffu, status) == 0);
Require(status[0] == 0 && status[15] == 0 && status[16] == 0x5a);
std::memset(status, 0x5a, sizeof(status));
Require(sceShareGetCurrentStatus(0, status) == invalidParam);
Require(status[0] == 0x5a);
Require(sceShareGetCurrentStatus(1, nullptr) == invalidParam);
return 0;
}

View File

@ -8,6 +8,7 @@ extern "C" {
char* APS5_VABI basename_nid_postfix(const char*);
int* APS5_VABI __error_nid_postfix();
std::size_t APS5_VABI strnlen_nid_postfix(const char*, std::size_t);
std::size_t APS5_VABI strnlen_s_nid_postfix(const char*, std::size_t);
char* APS5_VABI strncat_nid_postfix(char*, const char*, std::size_t);
char* APS5_VABI strpbrk_nid_postfix(const char*, const char*);
std::size_t APS5_VABI strcspn_nid_postfix(const char*, const char*);
@ -88,6 +89,8 @@ int main() {
Require(strnlen_nid_postfix(bounded, 0) == 0);
Require(strnlen_nid_postfix(bounded, sizeof(bounded)) == 3);
Require(strnlen_nid_postfix("a", 8) == 1);
Require(strnlen_s_nid_postfix(nullptr, 8) == 0);
Require(strnlen_s_nid_postfix("abc", 8) == 3 && strnlen_s_nid_postfix("abcdef", 4) == 4);
char truncated[] = "abXX";
Require(strlcat_nid_postfix(truncated, "cd", 2) == 4);
Require(std::strcmp(truncated, "abXX") == 0);

View File

@ -0,0 +1,218 @@
#include "prx/libSceFont/include/FontTypes.hpp"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <initializer_list>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
extern "C" {
int APS5_VABI sceFontMemoryInit(FontMemory*, void*, std::uint32_t, const FontMemoryInterface*, void*, FontMemoryDestroyFunction, void*);
int APS5_VABI sceFontCreateLibrary(const FontMemory*, const void*, FontLibrary*);
int APS5_VABI sceFontDestroyLibrary(FontLibrary*);
int APS5_VABI sceFontSupportSystemFonts(FontLibrary);
int APS5_VABI sceFontSupportExternalFonts(FontLibrary, std::uint32_t, std::uint32_t);
int APS5_VABI sceFontOpenFontSet(FontLibrary, std::uint32_t, std::uint32_t, const FontOpenDetail*, FontHandle*);
int APS5_VABI sceFontOpenFontMemory(FontLibrary, const void*, std::uint32_t, const FontOpenDetail*, FontHandle*);
int APS5_VABI sceFontOpenFontInstance(FontHandle, FontHandle, FontHandle*);
int APS5_VABI sceFontCloseFont(FontHandle);
int APS5_VABI sceFontSetScalePixel(FontHandle, float, float);
int APS5_VABI sceFontGetCharGlyphMetrics(FontHandle, std::uint32_t, FontGlyphMetrics*);
int APS5_VABI sceFontGetHorizontalLayout(FontHandle, FontHorizontalLayout*);
const void* APS5_VABI sceFontSelectLibraryFt(int);
}
static void Check(bool value, int line) {
if (!value) {
std::fprintf(stderr, "System font check failed at line %d\n", line);
std::abort();
}
}
#define Require(value) Check((value), __LINE__)
static int allocations = 0;
static void* APS5_VABI Allocate(void*, std::uint32_t size) {
++allocations;
return std::malloc(size);
}
static void APS5_VABI Release(void*, void* pointer) {
if (pointer) --allocations;
std::free(pointer);
}
static void Put16(std::vector<unsigned char>& out, std::uint32_t value) {
out.push_back(static_cast<unsigned char>(value >> 8));
out.push_back(static_cast<unsigned char>(value));
}
static void Put32(std::vector<unsigned char>& out, std::uint32_t value) {
Put16(out, value >> 16);
Put16(out, value & 0xFFFFu);
}
static void PutAll16(std::vector<unsigned char>& out, std::initializer_list<int> values) {
for (const int value : values) Put16(out, static_cast<std::uint32_t>(static_cast<std::uint16_t>(value)));
}
static std::vector<unsigned char> SquareGlyphFont(std::uint32_t codepoint, int width) {
std::vector<unsigned char> head;
Put32(head, 0x00010000u);
Put32(head, 0x00010000u);
Put32(head, 0);
Put32(head, 0x5F0F3CF5u);
PutAll16(head, {0x000B, 1000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, width, 800, 0, 8, 2, 0, 0});
std::vector<unsigned char> hhea;
Put32(hhea, 0x00010000u);
PutAll16(hhea, {800, -200, 0, width, 0, 0, width, 1, 0, 0, 0, 0, 0, 0, 0, 2});
std::vector<unsigned char> maxp;
Put32(maxp, 0x00010000u);
PutAll16(maxp, {2, 4, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0});
std::vector<unsigned char> hmtx;
PutAll16(hmtx, {width, 0, width, 100});
std::vector<unsigned char> glyf;
PutAll16(glyf, {1, 100, 0, width - 100, 700, 3, 0});
glyf.insert(glyf.end(), {1, 1, 1, 1});
PutAll16(glyf, {100, 0, width - 200, 0, 0, 700, 0, -700});
std::vector<unsigned char> loca;
PutAll16(loca, {0, 0, static_cast<int>(glyf.size() / 2)});
std::vector<unsigned char> cmap;
PutAll16(cmap, {0, 1, 3, 1});
Put32(cmap, 12);
PutAll16(cmap, {4, 32, 0, 4, 4, 1, 0, static_cast<int>(codepoint), 0xFFFF, 0, static_cast<int>(codepoint), 0xFFFF, 1 - static_cast<int>(codepoint), 1, 0, 0});
const std::vector<std::pair<const char*, const std::vector<unsigned char>*>> tables = {
{"cmap", &cmap}, {"glyf", &glyf}, {"head", &head}, {"hhea", &hhea}, {"hmtx", &hmtx}, {"loca", &loca}, {"maxp", &maxp}};
std::vector<unsigned char> font;
Put32(font, 0x00010000u);
PutAll16(font, {static_cast<int>(tables.size()), 64, 2, static_cast<int>(tables.size()) * 16 - 64});
std::uint32_t offset = 12 + static_cast<std::uint32_t>(tables.size()) * 16;
for (const auto& [tag, data] : tables) {
font.insert(font.end(), tag, tag + 4);
Put32(font, 0);
Put32(font, offset);
Put32(font, static_cast<std::uint32_t>(data->size()));
offset += (static_cast<std::uint32_t>(data->size()) + 3u) & ~3u;
}
for (const auto& [tag, data] : tables) {
font.insert(font.end(), data->begin(), data->end());
font.resize((font.size() + 3u) & ~std::size_t{3});
}
return font;
}
static void WriteFile(const std::filesystem::path& path, const std::vector<unsigned char>& bytes) {
std::ofstream file(path, std::ios::binary);
file.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
Require(static_cast<bool>(file));
}
static void SetFontDirectory(const std::filesystem::path& directory) {
#ifdef _WIN32
Require(_putenv_s("ANYPS5_SYSTEM_FONTS", directory.string().c_str()) == 0);
#else
Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.string().c_str(), 1) == 0);
#endif
}
static float AdvanceOf(FontHandle font, std::uint32_t code) {
FontGlyphMetrics metrics{};
Require(sceFontGetCharGlyphMetrics(font, code, &metrics) == SCE_FONT_OK);
return metrics.Horizontal.advance;
}
int main() {
constexpr std::uint32_t EuropeanLight = 0x180700C3u;
constexpr std::uint32_t EuropeanBold = 0x180700C7u;
constexpr std::uint32_t EuropeanItalic = 0x18170044u;
constexpr std::uint32_t ThaiMedium = 0x18071055u;
constexpr std::uint32_t VietnameseBold = 0x18070057u;
constexpr std::uint32_t JapaneseJg2Light = 0x1A0835D3u;
constexpr std::uint32_t ChineseGb = 0x180CB0D4u;
const std::filesystem::path root = std::filesystem::temp_directory_path() / "anyps5_guest_system_font";
std::filesystem::remove_all(root);
const std::filesystem::path empty = root / "empty";
const std::filesystem::path fonts = root / "fonts";
std::filesystem::create_directories(empty);
std::filesystem::create_directories(fonts);
WriteFile(fonts / "SST-Bold.otf", SquareGlyphFont('A', 700));
WriteFile(fonts / "NotoSans-Bold.ttf", SquareGlyphFont('A', 300));
WriteFile(fonts / "NotoSans-Light.ttf", SquareGlyphFont('A', 500));
WriteFile(fonts / "SST-Italic.otf", SquareGlyphFont('A', 520));
WriteFile(fonts / "NotoSansThai-Medium.ttf", SquareGlyphFont(0x0E01, 540));
WriteFile(fonts / "SSTVietnamese-Bold.otf", SquareGlyphFont(0x1EA0, 560));
WriteFile(fonts / "NotoSansCJK-Light.ttc", SquareGlyphFont(0x3042, 580));
const FontMemoryInterface iface{Allocate, Release, nullptr, nullptr, nullptr, nullptr};
FontMemory memory{};
Require(sceFontMemoryInit(&memory, nullptr, 0, &iface, nullptr, nullptr, nullptr) == SCE_FONT_OK);
FontLibrary library = nullptr;
Require(sceFontCreateLibrary(&memory, sceFontSelectLibraryFt(0), &library) == SCE_FONT_OK);
Require(sceFontSupportSystemFonts(library) == SCE_FONT_OK);
FontHandle font = nullptr;
SetFontDirectory(empty);
Require(sceFontOpenFontSet(library, EuropeanBold, 1, nullptr, &font) == SCE_FONT_ERROR_FONT_OPEN_FAILED && font == nullptr);
Require(sceFontOpenFontSet(library, 0x18070046u, 1, nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FONTSET && font == nullptr);
SetFontDirectory(root / "missing");
bool threw = false;
try {
sceFontOpenFontSet(library, EuropeanBold, 1, nullptr, &font);
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw);
SetFontDirectory(fonts);
const int beforeOpen = allocations;
Require(sceFontOpenFontSet(library, EuropeanBold, 1, nullptr, &font) == SCE_FONT_OK && font != nullptr);
Require(allocations - beforeOpen == 3);
Require(sceFontSetScalePixel(font, 100.0f, 100.0f) == SCE_FONT_OK);
Require(AdvanceOf(font, 'A') == 70.0f);
FontGlyphMetrics metrics{};
Require(sceFontGetCharGlyphMetrics(font, 'B', &metrics) == SCE_FONT_ERROR_NO_SUPPORT_GLYPH);
FontHorizontalLayout layout{};
Require(sceFontGetHorizontalLayout(font, &layout) == SCE_FONT_OK && layout.baselineOffset > 0.0f && layout.lineAdvance >= layout.baselineOffset);
FontHandle again = nullptr;
Require(sceFontOpenFontSet(library, EuropeanBold, 2, nullptr, &again) == SCE_FONT_OK && again != font);
FontHandle instance = nullptr;
Require(sceFontOpenFontInstance(font, nullptr, &instance) == SCE_FONT_OK && instance != nullptr);
Require(sceFontGetHorizontalLayout(instance, &layout) == SCE_FONT_OK && layout.baselineOffset > 0.0f);
const std::vector<std::pair<std::uint32_t, std::pair<std::uint32_t, float>>> expected = {
{EuropeanLight, {'A', 50.0f}}, {EuropeanItalic, {'A', 52.0f}}, {ThaiMedium, {0x0E01, 54.0f}},
{VietnameseBold, {0x1EA0, 56.0f}}, {JapaneseJg2Light, {0x3042, 58.0f}}};
for (const auto& [type, glyph] : expected) {
FontHandle set = nullptr;
Require(sceFontOpenFontSet(library, type, 1, nullptr, &set) == SCE_FONT_OK);
Require(sceFontSetScalePixel(set, 100.0f, 100.0f) == SCE_FONT_OK);
Require(AdvanceOf(set, glyph.first) == glyph.second);
Require(sceFontCloseFont(set) == SCE_FONT_OK);
}
Require(sceFontCloseFont(again) == SCE_FONT_OK);
FontHandle missing = nullptr;
Require(sceFontOpenFontSet(library, ChineseGb, 1, nullptr, &missing) == SCE_FONT_ERROR_FONT_OPEN_FAILED && missing == nullptr);
std::filesystem::remove(fonts / "SST-Bold.otf");
FontHandle substitute = nullptr;
Require(sceFontOpenFontSet(library, EuropeanBold, 3, nullptr, &substitute) == SCE_FONT_OK);
Require(sceFontSetScalePixel(substitute, 100.0f, 100.0f) == SCE_FONT_OK);
Require(AdvanceOf(substitute, 'A') == 30.0f);
Require(AdvanceOf(font, 'A') == 70.0f);
Require(sceFontSupportExternalFonts(library, 2, 0x52) == SCE_FONT_OK);
const std::vector<unsigned char> external = SquareGlyphFont('A', 900);
FontHandle memoryFont = nullptr;
Require(sceFontOpenFontMemory(library, external.data(), static_cast<std::uint32_t>(external.size()), nullptr, &memoryFont) == SCE_FONT_OK);
Require(sceFontSetScalePixel(memoryFont, 100.0f, 100.0f) == SCE_FONT_OK);
Require(AdvanceOf(memoryFont, 'A') == 90.0f);
Require(sceFontGetHorizontalLayout(memoryFont, &layout) == SCE_FONT_OK);
for (FontHandle handle : {font, instance, substitute, memoryFont}) Require(sceFontCloseFont(handle) == SCE_FONT_OK);
Require(sceFontDestroyLibrary(&library) == SCE_FONT_OK);
Require(allocations == 0);
std::filesystem::remove_all(root);
}

Some files were not shown because too many files have changed in this diff Show More