Skip to content

🎮 Extending the Plugin for Other Platforms

rafaelvaloto edited this page Oct 10, 2025 · 1 revision

The plugin is built with an extensible, multi-platform architecture at its core. This design allows developers to add support for new platforms (like consoles or custom hardware) by implementing a platform-specific connection layer, without altering the core logic of the plugin.

This guide will walk you through the process of adding support for a new platform, using PlayStation® as an example of a dedicated hardware implementation.

🗺️ Platform Architecture Overview

There are two main approaches for adding new platform support:

  1. Shared Implementation (FCommonsDeviceInfo): For platforms that use similar, open APIs (like SDL_hidapi), a single class can be shared. The plugin already does this for Linux and macOS, as their underlying device communication is nearly identical.
  2. Dedicated Implementation: For platforms with proprietary SDKs (like PlayStation®), a completely new, dedicated class must be created. This guide focuses on this advanced use case.

Guide: Adding a Dedicated Platform (PlayStation® Example)

Adding support for a new platform involves three main steps:

  1. ⚙️ Configure the Build File: Link the necessary platform-specific SDK libraries.
  2. 🧩 Implement the Connection Interface: Create a new C++ class to handle hardware communication using the official SDK.
  3. 🔌 Register the New Class: Register the new implementation with the plugin's factory.

⚙️ Step 1: Configure the Build File

First, you need to tell Unreal Engine's build system to link against the official SDK libraries for your target platform.

Example: Adding PlayStation® Support

Update WindowsDualsense_ds5w.Build.cs to include the platform's modules. When compiling for a console, Unreal Engine's build system already knows where to find the SDK, so you only need to reference the required module names.

// Source/WindowsDualsense_ds5w/WindowsDualsense_ds5w.Build.cs

// ... (existing code)

// Add this new block for PlayStation®
if (Target.Platform == UnrealTargetPlatform.Sony)
{
    // Add the necessary modules from the official PlayStation® SDK
    PublicDependencyModuleNames.Add("Pad"); // For controller input
    // Add other modules as needed, e.g., for haptics
}

🧩 Step 2: Implement the Connection Interface

Next, create the C++ class that will handle the actual communication with the controller using the SDK. This class must implement the IPlatformHardwareInfoInterface.

  1. Create the Header File (.h): It's good practice to place platform-specific code in its own folder.

    Path: Source/WindowsDualsense_ds5w/Public/Core/Platforms/Sony/PlayStationDeviceInfo.h

    // PlayStationDeviceInfo.h
    #pragma once
    #include "Core/Interfaces/PlatformHardwareInfoInterface.h"
    
    class FPlayStationDeviceInfo : public IPlatformHardwareInfoInterface
    {
    public:
        virtual void Read(FDeviceContext* Context) override;
        virtual void Write(FDeviceContext* Context) override;
        virtual void Detect(TArray<FDeviceContext>& Devices) override;
        virtual bool CreateHandle(FDeviceContext* Context) override;
        virtual void InvalidateHandle(FDeviceContext* Context) override;
    };
  2. Create the Source File (.cpp): Now, implement the functions declared in the header.

    Path: Source/WindowsDualsense_ds5w/Private/Core/Platforms/Sony/PlayStationDeviceInfo.cpp

    ⚠️ Important Warning for PlayStation® SDK Developers

    If you are implementing the interface for the official Sony PlayStation® SDK, do NOT submit your implementation in a Pull Request.

    The Sony SDK is proprietary and its code cannot be hosted in a public repository. The project is already configured with a .gitignore file in the Source/.../Platforms/Sony/ directory to prevent any files within it from being committed. This is to protect licensed developers from accidentally exposing confidential code.

    The following implementation details are provided as pseudocode and descriptions to guide licensed developers.

    Method Implementation Details

    Detect(TArray<FDeviceContext>& Devices)

    The plugin calls this method periodically to scan for available hardware.

    • Your Goal: Use the SDK to find all connected controllers. For each controller, populate an FDeviceContext struct and add it to the Devices array.
    // PSEUDOCODE IMPLEMENTATION
    void FPlayStationDeviceInfo::Detect(TArray<FDeviceContext>& Devices)
    {
        // Use the Sony SDK function `scePadGetHandleList()` to get an array of active controller handles.
        
        // For each handle returned by the SDK:
        // {
        //     FDeviceContext NewDeviceContext;
        //     NewDeviceContext.DeviceType = EDeviceType::DualSense;
        //     NewDeviceContext.IsConnected = true;
        //     NewDeviceContext.Handle = (FPlatformDeviceHandle)sdkControllerHandle; // Store the SDK handle
        //     Devices.Add(NewDeviceContext);
        // }
    }

    CreateHandle(FDeviceContext* Context)

    This method is called after a new device is found via Detect.

    • Your Goal: For the PlayStation® platform, the handle is already discovered during the Detect phase. This function can simply validate that the handle is present and return true.
    // PSEUDOCODE IMPLEMENTATION
    bool FPlayStationDeviceInfo::CreateHandle(FDeviceContext* Context)
    {
        // The handle from the SDK was already stored in the Detect() method.
        // No new handle needs to be created, just confirm it's valid.
        return Context && Context->Handle != INVALID_PLATFORM_HANDLE;
    }

    Read(FDeviceContext* Context) and Write(FDeviceContext* Context)

    These methods are called frequently to send and receive data.

    • Your Goal: Use the appropriate SDK functions to read the controller state or send haptic data.
    • Error Handling: If an SDK function indicates an error or disconnection, you must call InvalidateHandle(Context) to clean up.
    // PSEUDOCODE IMPLEMENTATION
    void FPlayStationDeviceInfo::Read(FDeviceContext* Context)
    {
        // 1. Declare a variable for the SDK's controller state structure (e.g., ScePadData).
        //
        // 2. Call the Sony SDK function `scePadReadState()` using the handle from `Context->Handle`.
        //    int result = scePadReadState(Context->Handle, &sdkPadData);
        //
        // 3. If the result indicates an error or disconnection:
        // {
        //     InvalidateHandle(Context); // Critical for cleanup
        //     return;
        // }
        //
        // 4. Copy the data from the SDK structure into the plugin's generic buffer.
        //    FMemory::Memcpy(Context->Buffer, &sdkPadData, sizeof(sdkPadData));
    }
    
    void FPlayStationDeviceInfo::Write(FDeviceContext* Context)
    {
        // 1. Map the generic output data from `Context->BufferOutput` to the SDK's vibration/trigger effect structure.
        //
        // 2. Call the relevant Sony SDK function, e.g., `scePadSetVibration()` or `scePadSetTriggerEffect()`.
        //
        // 3. If any SDK call fails, indicating a disconnection:
        // {
        //     InvalidateHandle(Context); // Critical for cleanup
        // }
    }

    InvalidateHandle(FDeviceContext* Context)

    This method is crucial for cleanup when a device is disconnected.

    • Your Goal: Close the device handle (if required by the SDK), clean up buffers, and reset the context state.
    // IMPLEMENTATION EXAMPLE
    void FPlayStationDeviceInfo::InvalidateHandle(FDeviceContext* Context)
    {
        if (Context && Context->Handle != INVALID_PLATFORM_HANDLE)
        {
            // If the SDK requires explicitly closing the handle, do so here.
            // For example: scePadClose(Context->Handle);
            
            // Reset the plugin's internal state
            Context->Handle = INVALID_PLATFORM_HANDLE;
            Context->IsConnected = false;
    
            // Clean up context resources
            Context->Path = nullptr;
            memset(Context->Buffer, 0, sizeof(Context->Buffer));
            memset(Context->BufferDS4, 0, sizeof(Context->BufferDS4));
            memset(Context->BufferOutput, 0, sizeof(Context->BufferOutput));
        }
    }

🔌 Step 3: Register Your New Implementation

The final step is to register your new class with the plugin's factory method.

Modify the Get() method in IPlatformHardwareInfoInterface.cpp to instantiate your new class. This line should remain commented in the public repository and only enabled by licensed developers in their local, private builds.

File: Source/WindowsDualsense_ds5w/Private/Core/Interfaces/PlatformHardwareInfoInterface.cpp

// IPlatformHardwareInfoInterface.cpp

// ... (includes)
#elif PLATFORM_SONY
// Include your private header here for local compilation
#include "Core/Platforms/Sony/PlayStationDeviceInfo.h" 
// ...

IPlatformHardwareInfoInterface& IPlatformHardwareInfoInterface::Get()
{
    if (!PlatformInfoInstance)
    {
        #if PLATFORM_WINDOWS
            PlatformInfoInstance = MakeUnique<FWindowsDeviceInfo>();
        #elif PLATFORM_LINUX || PLATFORM_MAC
            // Use the shared implementation for Linux and macOS
            PlatformInfoInstance = MakeUnique<FCommonsDeviceInfo>();
        #elif PLATFORM_SONY
            // For local compilation by licensed developers, uncomment the line below:
            // PlatformInfoInstance = MakeUnique<FPlayStationDeviceInfo>();
            PlatformInfoInstance = nullptr; // Default for public repo
        #endif
    }
    return *PlatformInfoInstance;
}

With these steps completed, any licensed developer can now integrate the plugin to have full controller support on PlayStation®, using a custom, native implementation.

Thank You!

Thank you for your interest in extending the plugin! If you have any questions, run into issues, or want to discuss the implementation, please feel free to open an Issue or start a Discussion on the repository. We welcome all contributions!


Back to the Main Repository