Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e1c9ed2
Create iruser.c
syelan34 Mar 4, 2025
59134cf
Create iruser.h
syelan34 Mar 4, 2025
97a8dc6
Update 3ds.h
syelan34 Mar 4, 2025
2100047
Fix all the stupid issues I had bc of translating from c++
syelan34 Mar 4, 2025
3b0bc2e
Move memory allocation to be handled by user, to align with the rest of
syelan34 Mar 31, 2025
d7700c2
Merge branch 'devkitPro:master' into master
syelan34 Mar 31, 2025
463354a
Add disconnecting status to enum
syelan34 Apr 12, 2025
66708c4
Rename IrUserPacket, IrUserConnectionStatus, and IrUserStatusInfo to
syelan34 Apr 13, 2025
ec9438b
Add IRUSER_WaitConnection implementation
syelan34 Apr 13, 2025
6835607
Fix WaitConnection command header
syelan34 Apr 13, 2025
d4d6cc9
Fix disconnecting status value
syelan34 Apr 13, 2025
83c8a49
Add IRUSER_GetConnectionStatus implementation
syelan34 Apr 13, 2025
0c8233b
Add IRUSER_GetConnectionRole implementation
syelan34 Apr 13, 2025
c47702b
Add preliminary TryingToConnectStatus implementation
syelan34 Apr 13, 2025
c730ee0
Fix packet parsing function
syelan34 Apr 15, 2025
3081d37
Mostly fix getting packets from shared mem, plus actually check the
syelan34 Apr 17, 2025
8eb9c7b
Add GetLatestReceive/SendErrorResult() implementation.
syelan34 Apr 17, 2025
2bdcec8
Resolved issues raised by @TuxSH.
syelan34 Sep 30, 2025
a4931d3
Fixed all the compile errors I should have checked before.
syelan34 Oct 1, 2025
d5adcb4
Slightly edit iruserParsePacket() to do the checksum check before cop…
syelan34 Oct 1, 2025
0fed262
Fix some extra compile errors that somehow didn't show up
syelan34 Oct 1, 2025
b1d1275
Changed iruserCirclePadProCStickRead() and iruserGetCirclePadProState…
syelan34 Oct 2, 2025
61c3c87
Fix accidental used of . instead of ->
syelan34 Oct 2, 2025
49c18c7
Fix iruserParsePacket reading payload large/small and length value in…
syelan34 Oct 2, 2025
289f5a4
Add preliminary iruserCPPRequestCalibrationData() implementation
syelan34 Oct 2, 2025
e44a832
Fix off-by-one error in iruserParsePacket() checksum calculation
syelan34 Oct 2, 2025
b646fc4
Fixed bit mask for payload length
syelan34 Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libctru/include/3ds.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ extern "C" {
#include <3ds/services/nim.h>
#include <3ds/services/nwmext.h>
#include <3ds/services/ir.h>
#include <3ds/services/iruser.h>
#include <3ds/services/ns.h>
#include <3ds/services/pmapp.h>
#include <3ds/services/pmdbg.h>
Expand Down
267 changes: 267 additions & 0 deletions libctru/include/3ds/services/iruser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
/**
* @file iruser.h
* @brief IRUSER service.
*/
#pragma once

#include <3ds/types.h>
#include <3ds/services/hid.h>

/// Connection status values for [`IRUSER_StatusInfo`].
typedef enum {
CNSTATUS_Disconnected = 0, /// Device is not connected
CNSTATUS_Connecting = 1, /// Waiting for device to connect
CNSTATUS_Connected = 2, /// Device is connected
CNSTATUS_Disconnecting = 3, /// Device is disconnecting
CNSTATUS_Unknown = 4 // /// Unknown status value
} IRUSER_ConnectionStatus;

/// Connection status values for [`IRUSER_StatusInfo`].
typedef enum {
Unk_1 = 1,
Unk_2 = 2,
Unk_3 = 3,
Unk_4 = 4
} IRUSER_TryingToConnectStatus;

typedef enum {
CNRole_1 = 1,
CNRole_2 = 2,
} IRUSER_ConnectionRole;

/// This struct holds a parsed copy of the ir:USER service status (from shared memory).
typedef struct {
/// The result of the last receive operation.
Result recv_err_result;
/// The result of the last send operation.
Result send_err_result;
/// The current connection status.
IRUSER_ConnectionStatus connection_status;
/// The status of the connection attempt.
u8 trying_to_connect_status;
/// The role of the device in the connection (value meaning is unknown).
u8 connection_role;
/// The machine ID of the device.
u8 machine_id;
/// The machine ID of the target device.
u8 target_machine_id;
/// The network ID of the connection.
u8 network_id;
/// Unknown field.
u8 unknown_field_2;
/// Unknown field.
u8 unknown_field_3;
} IRUSER_StatusInfo;

typedef struct {
u32 start_index;
u32 end_index;
u32 valid_packet_count;
u32 unknown_field;
} IRUSER_BufferInfo;

typedef struct {
u32 offset;
u32 length;
} IRUSER_PacketInfo;

/// A packet of data sent/received to/from the IR device.
/// `payload` should be initialized by the user to point to a buffer of at least 0x4000 bytes (the maximum payload size)
typedef struct {
/// The magic number of the packet. Should always be 0xA5.
u8 magic_number;
/// The destination network ID.
u8 destination_network_id;
/// The length of the payload.
size_t payload_length;
/// The payload data.
u8* payload;
/// The checksum of the packet.
u8 checksum;
} IRUSER_Packet;

/// Circle Pad Pro response packet holding the current device input signals and status.
typedef struct {
union {
struct {
/// The X value of the C-stick.
u16 c_stick_x;
/// The Y value of the C-stick.
u16 c_stick_y;
};
circlePosition csPos;
} cstick;
union {
u8 status_raw;
struct {
/// The battery level of the Circle Pad Pro.
u8 battery_level : 5;
/// Whether the ZL button is pressed.
bool zl_pressed : 1;
/// Whether the ZR button is pressed.
bool zr_pressed : 1;
/// Whether the R button is pressed.
bool r_pressed : 1;
} status;
};
/// Unknown field.
u8 unknown_field;
} circlePadProInputResponse;

/**
* @brief Initializes IRUSER.
* Allocates memory to use as shared_memory based on buffer_size and sets it as read only.
*
*/
Result iruserInit(u32 *sharedmem_addr, u32 sharedmem_size, size_t buffer_size, size_t packet_count);

/// Shuts down IRUSER. Frees shared memory.
void iruserExit();

/// Gets IRUSER service handle
Handle iruserGetServHandle();


IRUSER_StatusInfo iruserGetStatusInfo();

/**
* @brief Circle Pad Pro specific request.
* This will send a packet to the CPP requesting it to send back packets with the current device input values.
* @param period_ms Period at which to send another packet.
*/
Result iruserCPPRequestInputPolling(u8 period_ms);

/// This will let you directly read the ir:USER shared memory via a callback.
void iruserProcessSharedMemory(void(*process_fn)(u8*));

/**
* @brief Gets circle pad pro inputs state
* @param response Pointer to write data to
*/
bool iruserGetCirclePadProState(IRUSER_Packet* packet, circlePadProInputResponse* response);

/**
* @brief Reads c-stick position from circle pad pro
* @param pos Pointer to write data to
*/
bool iruserCirclePadProCStickRead(IRUSER_Packet* packet, circlePosition* pos);

/**
* @brief gets the packets received from the IR device.
* @param packets Buffer to write packet data into.
* @param numpackets Size of buffer (in packets).
* @return Actual number of (valid) packets received
*/
u32 iruserGetNPackets(IRUSER_Packet packets[], u32 numpackets);

/**
* @brief Initializes the IR session
* @brief IR uses shared memory in non-shared mode (puts less information in shared memory)
* @param recv_buffer_size Size of receiving buffer
* @param recv_packet_count
* @param send_buffer_size
* @param send_packet_count
* @param bitrate
*/
Result IRUSER_InitializeIrNop(size_t recv_buffer_size, size_t recv_packet_count, size_t send_buffer_size, size_t send_packet_count, u32 bitrate);

/// Shuts down the IR session
Result IRUSER_FinalizeIrNop();

/// Clears receive buffer
Result IRUSER_ClearReceiveBuffer();

/// Clears send buffer
Result IRUSER_ClearSendBuffer();

/**
* @brief Attempts to connect to device with given id and timeout.
* @param device_id ID of the device to connect to.
* @param timeout Timeout in milliseconds.
*/
Result IRUSER_WaitConnection(u8 target_id, u64 timeout);

/**
* @brief Attempts to connect to device with given id.
* @param device_id ID of the device to connect to.
*/
Result IRUSER_RequireConnection(u8 device_id);

/// Unknown.
Result IRUSER_AutoConnection();

/// Connects to any device
Result IRUSER_AnyConnection();

/// Closes the current IR connection.
Result IRUSER_Disconnect();

/**
* @brief Gets an event handle that activates when a packet is received.
* @param eventhandle Pointer to write the event handle to.
*/
Result IRUSER_GetReceiveEvent(Handle* eventhandle);

/**
* @brief Gets an event handle that activates when a packet is sent.
* @param eventhandle Pointer to write the event handle to.
*/
Result IRUSER_GetSendEvent(Handle* eventhandle);

/**
* @brief Gets an event handle that activates on connection status change.
* @param eventhandle Pointer to write the event handle to.
*/
Result IRUSER_GetConnectionStatusEvent(Handle* eventhandle);

/**
* @brief Sends data to connected device
* @brief Should be used when `size <= 0xFC`
* @param size Size of the buffer
* @param inbufptr Buffer to send
*/
Result IRUSER_SendIrNop(u32 size, u8* inbufptr);

/**
* @brief Sends data to connected device
* @brief Should be used when `size > 0xFC`
* @param size Size of the buffer
* @param inbufptr Buffer to send
*/
Result IRUSER_SendIrNopLarge(u32 size, u8* inbufptr);


Result IRUSER_ReceiveIrNop();
Result IRUSER_ReceiveIrNopLarge();


Result IRUSER_GetLatestReceiveErrorResult(Result* result);
Result IRUSER_GetLatestSendErrorResult(Result* result);
Result IRUSER_GetConnectionStatus(IRUSER_ConnectionStatus* status);
Result IRUSER_GetTryingToConnectStatus(IRUSER_TryingToConnectStatus* status);
Result IRUSER_GetReceiveSizeFreeAndUsed();
Result IRUSER_GetSendSizeFreeAndUsed();
Result IRUSER_GetConnectionRole(IRUSER_ConnectionRole* role);

/**
* @brief Initializes the IR session
* @brief IR uses shared memory in shared mode (puts more information in shared memory)
* @param recv_buffer_size Size of receiving buffer
* @param recv_packet_count
* @param send_buffer_size
* @param send_packet_count
* @param bitrate
*/
Result IRUSER_InitializeIrNopShared(size_t recv_buffer_size, size_t recv_packet_count, size_t send_buffer_size, size_t send_packet_count, u32 bitrate);

/**
* @brief Mark the last `packet_count` packets as processed, so their memory in the receive buffer can be reused.
* @param packet_count Number of packets to mark as processed
*/
Result IRUSER_ReleaseReceivedData(u32 packet_count);

/**
* @brief Sets own ID to `id`.
* @param id ID to set
*/
Result IRUSER_SetOwnMachineId(u8 id);
Loading