diff --git a/core/libs/prx/libSceBluetoothHid/CMakeLists.txt b/core/libs/prx/libSceBluetoothHid/CMakeLists.txt new file mode 100644 index 000000000..1f7328c36 --- /dev/null +++ b/core/libs/prx/libSceBluetoothHid/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(libSceBluetoothHid SHARED EXCLUDE_FROM_ALL Export.cpp) +target_include_directories(libSceBluetoothHid PRIVATE ${LIBS_INCLUDE_DIR}) +configure_windows_unwind(libSceBluetoothHid) + +if(BUILD_TESTING) + add_test_executable(guest_bluetooth_hid_tests ${LIBS_INCLUDE_DIR}/tests/GuestBluetoothHid.cpp) + target_include_directories(guest_bluetooth_hid_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_bluetooth_hid_tests PRIVATE libSceBluetoothHid libc) + configure_windows_unwind(guest_bluetooth_hid_tests) + add_test(NAME guest_bluetooth_hid COMMAND guest_bluetooth_hid_tests) + if(WIN32) + set_tests_properties(guest_bluetooth_hid PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endif() diff --git a/core/libs/prx/libSceBluetoothHid/Export.cpp b/core/libs/prx/libSceBluetoothHid/Export.cpp new file mode 100644 index 000000000..46e728e4f --- /dev/null +++ b/core/libs/prx/libSceBluetoothHid/Export.cpp @@ -0,0 +1,52 @@ +#include +#include +#include "SceTypes.hpp" +#include "prx/libc/include/General.hpp" + +#include + +extern "C" { + +std::int32_t APS5_VABI sceBluetoothHidInit() { + return 0; +} + +std::int32_t APS5_VABI sceBluetoothHidRegisterCallback(void* callback, std::int32_t flags, void* param) { + (void)flags; + (void)param; + if (callback == nullptr) throw std::invalid_argument(std::string(__func__) + ": null callback"); + return 0; +} + +std::int32_t APS5_VABI sceBluetoothHidUnregisterCallback() { + return 0; +} + +std::int32_t APS5_VABI sceBluetoothHidRegisterDevice(std::uint16_t vendorId, std::uint16_t productId) { + (void)vendorId; + (void)productId; + return 0; +} + +std::int32_t APS5_VABI sceBluetoothHidUnregisterDevice(std::uint16_t vendorId, std::uint16_t productId) { + (void)vendorId; + (void)productId; + return 0; +} + +int APS5_VABI sceBluetoothHidGetDeviceName() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceBluetoothHidGetInputReport() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +int APS5_VABI sceBluetoothHidSetOutputReport() { + NotImplemented_nid_no_patch(__func__); + return 0; +} + +} diff --git a/core/libs/tests/GuestBluetoothHid.cpp b/core/libs/tests/GuestBluetoothHid.cpp new file mode 100644 index 000000000..f85b1d9ae --- /dev/null +++ b/core/libs/tests/GuestBluetoothHid.cpp @@ -0,0 +1,58 @@ +#include "prx/libc/include/general/VabiMacros.hpp" + +#include +#include +#include +#include + +extern "C" { +std::int32_t APS5_VABI sceBluetoothHidInit(); +std::int32_t APS5_VABI sceBluetoothHidRegisterCallback(void* callback, std::int32_t flags, void* param); +std::int32_t APS5_VABI sceBluetoothHidUnregisterCallback(); +std::int32_t APS5_VABI sceBluetoothHidRegisterDevice(std::uint16_t vendorId, std::uint16_t productId); +std::int32_t APS5_VABI sceBluetoothHidUnregisterDevice(std::uint16_t vendorId, std::uint16_t productId); +int APS5_VABI sceBluetoothHidGetInputReport(); +} + +namespace { + +int callbackCalls = 0; + +void APS5_VABI Callback() { + ++callbackCalls; +} + +void Require(bool condition, const char* message) { + if (!condition) { + std::fprintf(stderr, "BluetoothHid: %s\n", message); + std::abort(); + } +} + +} + +int main() { + Require(sceBluetoothHidInit() == 0, "initialization failed"); + std::uint8_t param[16]{}; + Require(sceBluetoothHidRegisterCallback(reinterpret_cast(&Callback), 0, param) == 0, "callback registration failed"); + Require(sceBluetoothHidRegisterDevice(0x44f, 0x1000) == 0, "device registration failed"); + Require(callbackCalls == 0, "a device connection was reported"); + bool threw = false; + try { + sceBluetoothHidRegisterCallback(nullptr, 0, param); + } catch (const std::invalid_argument&) { + threw = true; + } + Require(threw, "a null callback was accepted"); + threw = false; + try { + sceBluetoothHidGetInputReport(); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw, "an input report without a device did not throw"); + Require(sceBluetoothHidUnregisterDevice(0x44f, 0x1000) == 0, "device unregistration failed"); + Require(sceBluetoothHidUnregisterCallback() == 0, "callback unregistration failed"); + std::puts("BluetoothHid tests passed"); + return 0; +} diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index ad9bd8ee8..ed16c87e1 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -21,6 +21,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceAudioPropagation](../../core/libs/prx/libSceAudioPropagation/Export.cpp) implements only the no-acoustic-propagation path: the system keeps a 16-byte header in the title's CPU memory (no GPU memory), rooms, portals, sources and materials are tracked handles, attributes are validated and have no effect, no rays are requested and sources report no audio paths. Ray results, path calculation and audio paths are accepted only when empty and throw otherwise; `sceAudioPropagationSourceRender` writes silence (zeroes) to each source's output buffer for the given size, without a dry signal. Objects still alive when their system is destroyed become unusable, and their later destroy or unregister is a no-op: PPSA21567 releases its reference-counted room, portal and source owners just before `sceAudioPropagationSystemDestroy`, so the order for every object is not proven. Occlusion, reflections and reverb from level geometry are absent; in-game behaviour remains unverified. - [libSceNpCommerce](../../core/libs/prx/libSceNpCommerce/Export.cpp) - the PS Store icon show/hide calls and `sceNpCommerceSetPsStoreIconLayout` do nothing - [libSceSystemService](../../core/libs/prx/libSceSystemService/Export.cpp) - `sceSystemServicePowerTick`, `sceSystemServiceReportAbnormalTermination`, `sceSystemServiceDisableMusicPlayer` and `sceSystemServiceReenableMusicPlayer` return success and do nothing: the host has no auto power-off timer to reset, no system crash reporter to notify and no system music player to pause or resume +- [libSceBluetoothHid](../../core/libs/prx/libSceBluetoothHid/Export.cpp) implements only the no-device path: initialization and callback and device registration succeed, the callback is never called because no device connects, and calls that need a connected device throw - [sceVideoOutOpen](../../core/libs/prx/libSceVideoOut/src/Output.cpp) validates the priority and CPU affinity that the open param requests for the VideoOut service thread but does not apply them: the port's present and vblank threads are host threads, and guest priorities and affinities do not reach host scheduling - Without `VK_KHR_fragment_shader_barycentric` the shader recompiler [does not load the barycentric VGPRs](../../core/shader/recompiler/Translation/src/InstructionTranslator.cpp): [`v_interp_p1_f32` is dropped and `v_interp_p2_f32` reads the attribute Vulkan interpolated](../../core/shader/recompiler/Translation/src/AttributeInstructions.cpp), so pixel shaders that use the barycentrics for anything other than `v_interp` get wrong values. - [libSceAudio3d](../../core/libs/prx/libSceAudio3d/Export.cpp) - the port produces no sound: objects can be reserved but the library has no bed exports or `sceAudio3dObjectSetAttributes` to give them audio, so `sceAudio3dPortPush` only paces the queue at `granularity` samples per frame at 48 kHz. `sceAudio3dPortSetAttribute` accepts the late reverb level and the downmix spread attributes and ignores them @@ -38,6 +39,7 @@ Throughout the project, every function at every stage either **does exactly what - PPSA01341 imports declared without parameters, signatures unknown: [sceAgcSetSemaphoreMemory](../../core/libs/prx/libSceAgc/Unimplemented.cpp), [sceAgcDriverRegisterMultipleResources](../../core/libs/prx/libSceAgcDriver/Unimplemented.cpp). Names from the shadPS4 aerolib NID list - [sceAppContentDownloadDataGetAvailableSpaceKb](../../core/libs/prx/libSceAppContent/Export.cpp) (libSceAppContent) - a title whose `param.json` declares `downloadDataSize` 0 gets 0 KB and OK; the console's behaviour for a title without a download data area is unverified (it may return an error instead). KytyPS5 does not special-case it, and shadPS4 stubs 1048576 KB - [7CxI50-xlCk, pMxXhNozUX8](../../core/libs/prx/libSceNpPartner001/Export.cpp) (libSceNpPartner001) - unknown names and signatures, imported by PPSA23566; declared without parameters +- [libSceBluetoothHid](../../core/libs/prx/libSceBluetoothHid/Export.cpp) (libSceBluetoothHid) - signatures taken from PPSA01317: `sceBluetoothHidInit()` and `sceBluetoothHidUnregisterCallback()` without arguments, `sceBluetoothHidRegisterCallback(callback, 0, param)` with a 16-byte param, and `sceBluetoothHidRegisterDevice` / `sceBluetoothHidUnregisterDevice(vendorId, productId)`; the report and device name exports are declared without parameters - [sceAgcWaitRegMemPatchMask](../../core/libs/prx/libSceAgc/Patch/src/WaitRegMem.cpp) (libSceAgc) - follows `sceAgcWaitRegMemPatchReference`: the mask is taken as a 32-bit value and written to the low mask word of a 32- or 64-bit wait, as [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) does; [sharpemu](https://github.com/sharpemu/sharpemu/blob/main/src/SharpEmu.Libs/Agc/AgcExports.PacketPatching.cs) writes both words of a 64-bit wait's mask. A mask above 32 bits throws - [0GAw7SmkwII, 1ic5q-kdOsc, 5z2gBlqxJ+0, Kyy1baXgaVU, ZLL31lzzxr4, gMduXCLYrNg, lrJwpLjKXRc, m9JLPc3wOQw, o+NM86gEwFE](../../core/libs/prx/libScePsml_debug/Export.cpp) (libScePsml_mfsr2) - unknown names and signatures, imported by PPSA23566; declared without parameters - [sceAgcDcbSetCfRegisterDirect, sceAgcDcbSetCfRegisterRangeDirect](../../core/libs/prx/libSceAgc/DcbState/src/Registers.cpp) (libSceAgc) - signatures follow [OpenAGC](https://github.com/OpenAGC/OpenAGC/blob/main/src/game_compat.c) and [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Agc/SceAgc.cs), which agree; the SET_CONFIG_REG (0x68) packet is OpenAGC's. The driver does not execute SET_CONFIG_REG, so a submitted packet throws