diff --git a/core/libs/prx/libSceUsbd/Export.cpp b/core/libs/prx/libSceUsbd/Export.cpp index a67889f5c..d0be202ca 100644 --- a/core/libs/prx/libSceUsbd/Export.cpp +++ b/core/libs/prx/libSceUsbd/Export.cpp @@ -50,6 +50,14 @@ static_assert(offsetof(UsbdTransfer, timeout) == 12 && offsetof(UsbdTransfer, ca void* g_emptyDeviceList[1] = {nullptr}; +std::string AbsentDevice(const char* function) { + return std::string(function) + ": the device list is always empty, so the device was not returned by sceUsbdGetDeviceList"; +} + +std::string UnopenedHandle(const char* function) { + return std::string(function) + ": no device can be opened, so the handle was not returned by sceUsbdOpen"; +} + } extern "C" { @@ -116,99 +124,112 @@ void APS5_VABI sceUsbdFreeConfigDescriptor(void* config) { if (config != nullptr) throw std::runtime_error(std::string(__func__) + ": descriptor was not returned by this library"); } -int APS5_VABI sceUsbdAttachKernelDriver() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdAttachKernelDriver(void* deviceHandle, std::int32_t interfaceNumber) { + (void)interfaceNumber; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdCheckConnected() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdCheckConnected(void* deviceHandle) { + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdClaimInterface() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdClaimInterface(void* deviceHandle, std::int32_t interfaceNumber) { + (void)interfaceNumber; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdClose() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceUsbdClose(void* deviceHandle) { + if (deviceHandle != nullptr) throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdControlTransfer() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdControlTransfer(void* deviceHandle, std::uint8_t requestType, std::uint8_t request, std::uint16_t value, std::uint16_t index, std::uint8_t* data, std::uint16_t length, std::uint32_t timeout) { + (void)requestType; + (void)request; + (void)value; + (void)index; + (void)timeout; + if (deviceHandle == nullptr || (length != 0 && data == nullptr)) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdGetActiveConfigDescriptor() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdGetActiveConfigDescriptor(void* device, void** config) { + (void)config; + if (device == nullptr) throw std::runtime_error(std::string(__func__) + ": null device"); + throw std::runtime_error(AbsentDevice(__func__)); } -int APS5_VABI sceUsbdGetBusNumber() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint8_t APS5_VABI sceUsbdGetBusNumber(void* device) { + if (device == nullptr) return 0; + throw std::runtime_error(AbsentDevice(__func__)); } -int APS5_VABI sceUsbdGetConfigDescriptor() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdGetConfigDescriptor(void* device, std::uint8_t configIndex, void** config) { + (void)configIndex; + if (device == nullptr || config == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(AbsentDevice(__func__)); } -int APS5_VABI sceUsbdGetDeviceAddress() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::uint8_t APS5_VABI sceUsbdGetDeviceAddress(void* device) { + if (device == nullptr) return 0; + throw std::runtime_error(AbsentDevice(__func__)); } -int APS5_VABI sceUsbdGetDeviceDescriptor() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdGetDeviceDescriptor(void* device, void* descriptor) { + if (device == nullptr || descriptor == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(AbsentDevice(__func__)); } -int APS5_VABI sceUsbdGetStringDescriptor() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdGetStringDescriptor(void* deviceHandle, std::uint8_t descriptorIndex, std::uint16_t languageId, std::uint8_t* data, std::int32_t length) { + (void)descriptorIndex; + (void)languageId; + if (deviceHandle == nullptr || data == nullptr || length < 1) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdKernelDriverActive() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdKernelDriverActive(void* deviceHandle, std::int32_t interfaceNumber) { + (void)interfaceNumber; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdOpen() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdOpen(void* device, void** deviceHandle) { + if (device == nullptr) throw std::runtime_error(std::string(__func__) + ": null device"); + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(AbsentDevice(__func__)); } -int APS5_VABI sceUsbdRefDevice() { - NotImplemented_nid_no_patch(__func__); - return 0; +void* APS5_VABI sceUsbdRefDevice(void* device) { + if (device == nullptr) return nullptr; + throw std::runtime_error(AbsentDevice(__func__)); } -int APS5_VABI sceUsbdReleaseInterface() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdReleaseInterface(void* deviceHandle, std::int32_t interfaceNumber) { + (void)interfaceNumber; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdResetDevice() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdResetDevice(void* deviceHandle) { + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdSetConfiguration() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdSetConfiguration(void* deviceHandle, std::int32_t configuration) { + (void)configuration; + if (deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdSubmitTransfer() { - NotImplemented_nid_no_patch(__func__); - return 0; +std::int32_t APS5_VABI sceUsbdSubmitTransfer(UsbdTransfer* transfer) { + if (transfer == nullptr || transfer->deviceHandle == nullptr) return SCE_USBD_ERROR_INVALID_ARG; + throw std::runtime_error(UnopenedHandle(__func__)); } -int APS5_VABI sceUsbdUnrefDevice() { - NotImplemented_nid_no_patch(__func__); - return 0; +void APS5_VABI sceUsbdUnrefDevice(void* device) { + if (device != nullptr) throw std::runtime_error(AbsentDevice(__func__)); } } diff --git a/core/libs/tests/GuestUsbd.cpp b/core/libs/tests/GuestUsbd.cpp index 086487a48..e91e1da8c 100644 --- a/core/libs/tests/GuestUsbd.cpp +++ b/core/libs/tests/GuestUsbd.cpp @@ -21,7 +21,25 @@ void APS5_VABI sceUsbdExit(); std::int64_t APS5_VABI sceUsbdGetDeviceList(void*** list); void APS5_VABI sceUsbdFreeDeviceList(void** list, std::int32_t unrefDevices); std::int32_t APS5_VABI sceUsbdHandleEventsTimeout(const UsbdTimeval* timeout); -int APS5_VABI sceUsbdOpen(); +std::int32_t APS5_VABI sceUsbdOpen(void* device, void** deviceHandle); +void APS5_VABI sceUsbdClose(void* deviceHandle); +void* APS5_VABI sceUsbdRefDevice(void* device); +void APS5_VABI sceUsbdUnrefDevice(void* device); +std::uint8_t APS5_VABI sceUsbdGetBusNumber(void* device); +std::uint8_t APS5_VABI sceUsbdGetDeviceAddress(void* device); +std::int32_t APS5_VABI sceUsbdGetDeviceDescriptor(void* device, void* descriptor); +std::int32_t APS5_VABI sceUsbdGetConfigDescriptor(void* device, std::uint8_t configIndex, void** config); +std::int32_t APS5_VABI sceUsbdGetActiveConfigDescriptor(void* device, void** config); +std::int32_t APS5_VABI sceUsbdClaimInterface(void* deviceHandle, std::int32_t interfaceNumber); +std::int32_t APS5_VABI sceUsbdReleaseInterface(void* deviceHandle, std::int32_t interfaceNumber); +std::int32_t APS5_VABI sceUsbdSetConfiguration(void* deviceHandle, std::int32_t configuration); +std::int32_t APS5_VABI sceUsbdResetDevice(void* deviceHandle); +std::int32_t APS5_VABI sceUsbdCheckConnected(void* deviceHandle); +std::int32_t APS5_VABI sceUsbdKernelDriverActive(void* deviceHandle, std::int32_t interfaceNumber); +std::int32_t APS5_VABI sceUsbdAttachKernelDriver(void* deviceHandle, std::int32_t interfaceNumber); +std::int32_t APS5_VABI sceUsbdControlTransfer(void* deviceHandle, std::uint8_t requestType, std::uint8_t request, std::uint16_t value, std::uint16_t index, std::uint8_t* data, std::uint16_t length, std::uint32_t timeout); +std::int32_t APS5_VABI sceUsbdGetStringDescriptor(void* deviceHandle, std::uint8_t descriptorIndex, std::uint16_t languageId, std::uint8_t* data, std::int32_t length); +std::int32_t APS5_VABI sceUsbdSubmitTransfer(void* transfer); void* APS5_VABI sceUsbdAllocTransfer(std::int32_t isoPackets); void APS5_VABI sceUsbdFreeTransfer(void* transfer); void APS5_VABI sceUsbdFillInterruptTransfer(void* transfer, void* deviceHandle, std::uint8_t endpoint, std::uint8_t* buffer, std::int32_t length, void* callback, void* userData, std::uint32_t timeout); @@ -41,6 +59,16 @@ void Require(bool condition, const char* message) { } } +template +bool Throws(TCall call) { + try { + call(); + } catch (const std::runtime_error&) { + return true; + } + return false; +} + } int main() { @@ -57,13 +85,37 @@ int main() { const auto start = std::chrono::steady_clock::now(); Require(sceUsbdHandleEventsTimeout(&timeout) == 0, "event handling failed"); Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(45), "event handling returned before the timeout"); - bool threw = false; - try { - sceUsbdOpen(); - } catch (const std::runtime_error&) { - threw = true; - } - Require(threw, "opening a device did not throw"); + int foreign = 0; + void* handle = &foreign; + Require(sceUsbdOpen(&foreign, nullptr) == invalidArgument, "null handle output accepted"); + Require(Throws([&] { sceUsbdOpen(nullptr, &handle); }), "opening a null device did not throw"); + Require(Throws([&] { sceUsbdOpen(&foreign, &handle); }), "opening a device that was never listed did not throw"); + sceUsbdClose(nullptr); + Require(Throws([&] { sceUsbdClose(&foreign); }), "closing a handle that was never opened did not throw"); + Require(sceUsbdRefDevice(nullptr) == nullptr, "null device was referenced"); + sceUsbdUnrefDevice(nullptr); + Require(Throws([&] { sceUsbdRefDevice(&foreign); }), "referencing a device that was never listed did not throw"); + Require(Throws([&] { sceUsbdUnrefDevice(&foreign); }), "unreferencing a device that was never listed did not throw"); + Require(sceUsbdGetBusNumber(nullptr) == 0 && sceUsbdGetDeviceAddress(nullptr) == 0, "null device reported a bus or address"); + Require(Throws([&] { sceUsbdGetBusNumber(&foreign); }), "bus number of a device that was never listed did not throw"); + std::uint8_t descriptor[18] = {}; + void* config = &foreign; + Require(sceUsbdGetDeviceDescriptor(nullptr, descriptor) == invalidArgument, "null device descriptor accepted"); + Require(sceUsbdGetDeviceDescriptor(&foreign, nullptr) == invalidArgument, "null descriptor output accepted"); + Require(sceUsbdGetConfigDescriptor(nullptr, 0, &config) == invalidArgument, "null config device accepted"); + Require(sceUsbdGetConfigDescriptor(&foreign, 0, nullptr) == invalidArgument, "null config output accepted"); + Require(Throws([&] { sceUsbdGetConfigDescriptor(&foreign, 0, &config); }), "config of a device that was never listed did not throw"); + Require(Throws([&] { sceUsbdGetActiveConfigDescriptor(nullptr, &config); }), "active config of a null device did not throw"); + Require(sceUsbdClaimInterface(nullptr, 0) == invalidArgument && sceUsbdReleaseInterface(nullptr, 0) == invalidArgument, "null handle interface call accepted"); + Require(sceUsbdSetConfiguration(nullptr, 1) == invalidArgument && sceUsbdResetDevice(nullptr) == invalidArgument, "null handle configuration or reset accepted"); + Require(sceUsbdCheckConnected(nullptr) == invalidArgument, "null handle connection check accepted"); + Require(sceUsbdKernelDriverActive(nullptr, 0) == invalidArgument && sceUsbdAttachKernelDriver(nullptr, 0) == invalidArgument, "null handle kernel driver call accepted"); + Require(sceUsbdControlTransfer(nullptr, 0x80, 6, 0x100, 0, descriptor, 18, 1000) == invalidArgument, "null handle control transfer accepted"); + Require(sceUsbdControlTransfer(&foreign, 0x80, 6, 0x100, 0, nullptr, 18, 1000) == invalidArgument, "control transfer without data accepted"); + Require(Throws([&] { sceUsbdControlTransfer(&foreign, 0x80, 6, 0x100, 0, descriptor, 18, 1000); }), "control transfer on a handle that was never opened did not throw"); + Require(sceUsbdGetStringDescriptor(nullptr, 1, 0x409, descriptor, 18) == invalidArgument, "null handle string descriptor accepted"); + Require(sceUsbdGetStringDescriptor(&foreign, 1, 0x409, descriptor, 0) == invalidArgument, "empty string descriptor buffer accepted"); + Require(Throws([&] { sceUsbdClaimInterface(&foreign, 0); }), "claiming on a handle that was never opened did not throw"); auto* transfer = static_cast(sceUsbdAllocTransfer(2)); Require(transfer != nullptr, "transfer allocation failed"); Require(*reinterpret_cast(transfer + 56) == 2, "iso packet count not stored"); @@ -75,6 +127,10 @@ int main() { Require(*reinterpret_cast(transfer + 12) == 250 && *reinterpret_cast(transfer + 20) == 8, "timeout or length not filled"); Require(*reinterpret_cast(transfer + 40) == &user && *reinterpret_cast(transfer + 48) == buffer, "user data or buffer not filled"); Require(sceUsbdCancelTransfer(transfer) == static_cast(0x80240005), "cancelling an unsubmitted transfer did not report not found"); + Require(sceUsbdSubmitTransfer(nullptr) == invalidArgument, "null transfer submitted"); + Require(sceUsbdSubmitTransfer(transfer) == invalidArgument, "transfer without a handle submitted"); + sceUsbdFillInterruptTransfer(transfer, &foreign, 0x81, buffer, 8, reinterpret_cast(&Require), &user, 250); + Require(Throws([&] { sceUsbdSubmitTransfer(transfer); }), "submitting on a handle that was never opened did not throw"); sceUsbdFreeTransfer(transfer); sceUsbdFreeTransfer(nullptr); Require(sceUsbdEventHandlingOk() == 1, "event handling not allowed"); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 0c72b62a7..650039d4a 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -25,7 +25,7 @@ Throughout the project, every function at every stage either **does exactly what - [libSceMbus](../../core/libs/prx/libSceMbus/Export.cpp) implements only the no-device path: initialization and termination succeed, no device events are reported and device queries return no entries - [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 -- [libSceUsbd](../../core/libs/prx/libSceUsbd/Export.cpp) implements only the no-device path: `sceUsbdInit` and `sceUsbdExit` succeed without a host USB context, the device list is always empty and event handling waits for its timeout; calls that need a device throw. Transfers follow the `libusb_transfer` layout from [libusb.h](https://github.com/libusb/libusb/blob/master/libusb/libusb.h), which [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/usbd/usbd.h) passes to titles as `SceUsbdTransfer`: `sceUsbdAllocTransfer` zero-allocates one with its iso packet count (a negative count returns null), `sceUsbdFillInterruptTransfer` fills the fields `libusb_fill_interrupt_transfer` sets, and `sceUsbdCancelTransfer` reports `LIBUSB_ERROR_NOT_FOUND` mapped to `0x80240005` by shadPS4's `0x80240000 - error` rule, since no transfer can be submitted. `sceUsbdEventHandlingOk` returns 1 because no other thread handles events, and `sceUsbdFreeConfigDescriptor` accepts only null because no descriptor is ever returned. The layout and error codes are unverified on PS5 +- [libSceUsbd](../../core/libs/prx/libSceUsbd/Export.cpp) implements only the no-device path: `sceUsbdInit` and `sceUsbdExit` succeed without a host USB context, the device list is always empty and event handling waits for its timeout; calls that need a device throw. Transfers follow the `libusb_transfer` layout from [libusb.h](https://github.com/libusb/libusb/blob/master/libusb/libusb.h), which [shadPS4](https://github.com/shadps4-emu/shadPS4/blob/main/src/core/libraries/usbd/usbd.h) passes to titles as `SceUsbdTransfer`: `sceUsbdAllocTransfer` zero-allocates one with its iso packet count (a negative count returns null), `sceUsbdFillInterruptTransfer` fills the fields `libusb_fill_interrupt_transfer` sets, and `sceUsbdCancelTransfer` reports `LIBUSB_ERROR_NOT_FOUND` mapped to `0x80240005` by shadPS4's `0x80240000 - error` rule, since no transfer can be submitted. `sceUsbdEventHandlingOk` returns 1 because no other thread handles events, and `sceUsbdFreeConfigDescriptor` accepts only null because no descriptor is ever returned. The device and handle calls take the argument lists of FreeBSD 11.4 libusb ([libusb.h](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb.h)) and return `0x80240002` for the null arguments it rejects with `LIBUSB_ERROR_INVALID_PARAM` ([libusb10.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10.c), [libusb10_desc.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10_desc.c), [libusb10_io.c](https://github.com/freebsd/freebsd-src/blob/d106002967eff712894ab8b5337bbbff106ce5e0/lib/libusb/libusb10_io.c)); a null device or handle in the calls it treats as null-safe returns 0, null or nothing, as there. `sceUsbdOpen` and `sceUsbdGetActiveConfigDescriptor` throw on a null device, which libusb dereferences. Any other device or handle was never returned by this library, so those calls throw. The layout, the argument lists and the error codes are unverified on PS5 - [libSceSystemService](../../core/libs/prx/libSceSystemService/Export.cpp) - `sceSystemServicePowerTick`, `sceSystemServiceReportAbnormalTermination`, `sceSystemServiceDisableMusicPlayer`, `sceSystemServiceReenableMusicPlayer`, `sceSystemServiceDisableMediaPlay` and `sceSystemServiceReenableMediaPlay` return success and do nothing: the host has no auto power-off timer to reset, no system crash reporter to notify, no system music player to pause or resume and no system media playback to disable. PPSA01521 calls `sceSystemServiceDisableMediaPlay` during start-up, and its wrappers for both pass no arguments - [sceSystemServiceLaunchWebBrowser](../../core/libs/prx/libSceSystemService/Export.cpp) (libSceSystemService) - AnyPS5 launches no web browser, so the call fails and nothing is shown. RommPS PPSA76677 then tells the player to scan its QR code instead - [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