From fb3afd33e49c443cd8c5fd7e8ed0a20a07a6f8ac Mon Sep 17 00:00:00 2001
From: "Ryan C. Gordon" <[EMAIL REDACTED]>
Date: Mon, 22 Jun 2026 09:27:23 -0400
Subject: [PATCH] n3ds: Treat the d-pad as a hat switch at the SDL_Joystick
level.
Reference Issue #14830.
---
src/joystick/SDL_gamepad_db.h | 2 +-
src/joystick/n3ds/SDL_sysjoystick.c | 117 +++++++++++++++++++---------
2 files changed, 80 insertions(+), 39 deletions(-)
diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h
index 1a834455ead67..c184eefe237a3 100644
--- a/src/joystick/SDL_gamepad_db.h
+++ b/src/joystick/SDL_gamepad_db.h
@@ -877,7 +877,7 @@ static const char *s_GamepadMappings[] = {
"0000000050535669746120436f6e7400,PSVita Controller,crc:d598,a:b2,b:b1,back:b10,dpdown:b6,dpleft:b7,dpright:b9,dpup:b8,leftshoulder:b4,leftstick:b14,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,",
#endif
#ifdef SDL_JOYSTICK_N3DS
- "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b1,b:b0,back:b2,dpdown:b7,dpleft:b5,dpright:b4,dpup:b6,leftshoulder:b9,lefttrigger:b14,leftx:a0,lefty:a1,rightshoulder:b8,righttrigger:b15,rightx:a2,righty:a3,start:b3,x:b11,y:b10,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
+ "000000004e696e74656e646f20334400,Nintendo 3DS,crc:3210,a:b1,b:b0,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:b10,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b11,rightx:a2,righty:a3,start:b3,x:b7,y:b6,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,",
#endif
NULL
};
diff --git a/src/joystick/n3ds/SDL_sysjoystick.c b/src/joystick/n3ds/SDL_sysjoystick.c
index fda19dd2179d5..54bffa5c77f48 100644
--- a/src/joystick/n3ds/SDL_sysjoystick.c
+++ b/src/joystick/n3ds/SDL_sysjoystick.c
@@ -28,7 +28,8 @@
#include "../SDL_sysjoystick.h"
-#define NB_BUTTONS 23
+#define NB_BUTTONS 23 // physical buttons on the device (although we treat four of these, the dpad, as a hat switch).
+#define N3DS_HAT_MASK 0xF0 // 0xF0==d-pad bits, which we handle elsewhere, so mask them out here.
/*
N3DS sticks values are roughly within +/-160
@@ -54,8 +55,9 @@ static inline int Correct_Axis_Y(int Y) {
return Correct_Axis_X(-Y);
}
-static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick);
-static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick);
+static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state);
+static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state);
+static void UpdateN3DSHat(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_down_state, u32 current_down_state, u32 previous_up_state, u32 current_up_state);
static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick);
static void UpdateN3DSCStick(Uint64 timestamp, SDL_Joystick *joystick);
@@ -89,9 +91,9 @@ static SDL_JoystickID N3DS_JoystickGetDeviceInstanceID(int device_index)
static bool N3DS_JoystickOpen(SDL_Joystick *joystick, int device_index)
{
- joystick->nbuttons = NB_BUTTONS;
+ joystick->nbuttons = NB_BUTTONS - 4; // -4 for the dpad (which we treat as a hat).
joystick->naxes = 4;
- joystick->nhats = 0;
+ joystick->nhats = 1; // treat the dpad as a hat.
return true;
}
@@ -103,46 +105,85 @@ static bool N3DS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool enabled)
static void N3DS_JoystickUpdate(SDL_Joystick *joystick)
{
- Uint64 timestamp = SDL_GetTicksNS();
-
- UpdateN3DSPressedButtons(timestamp, joystick);
- UpdateN3DSReleasedButtons(timestamp, joystick);
+ static u32 previous_down_state = 0;
+ static u32 previous_up_state = 0;
+ const Uint64 timestamp = SDL_GetTicksNS();
+ const u32 current_down_state = hidKeysDown();
+ const u32 current_up_state = hidKeysUp();
+
+ UpdateN3DSPressedButtons(timestamp, joystick, previous_down_state, current_down_state);
+ UpdateN3DSReleasedButtons(timestamp, joystick, previous_up_state, current_up_state);
+ UpdateN3DSHat(timestamp, joystick, previous_down_state, current_down_state, previous_up_state, current_up_state);
UpdateN3DSCircle(timestamp, joystick);
UpdateN3DSCStick(timestamp, joystick);
+
+ previous_down_state = current_down_state;
+ previous_up_state = current_up_state;
}
-static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick)
+static void UpdateN3DSPressedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state)
{
- static u32 previous_state = 0;
- u32 updated_down;
- u32 current_state = hidKeysDown();
- updated_down = previous_state ^ current_state;
+ const u32 updated_down = (previous_state ^ current_state) & ~N3DS_HAT_MASK;
if (updated_down) {
- for (Uint8 i = 0; i < joystick->nbuttons; i++) {
- if (current_state & BIT(i) & updated_down) {
- SDL_SendJoystickButton(timestamp, joystick, i, true);
+ Uint8 buttonidx = 0;
+ Uint8 i = 0;
+ while (i < joystick->nbuttons) {
+ if ((buttonidx < 4) || (buttonidx > 7)) { // skip dpad (we treat it as a hat).
+ if (current_state & BIT(buttonidx) & updated_down) {
+ SDL_SendJoystickButton(timestamp, joystick, i, true);
+ }
+ i++;
}
+ buttonidx++;
}
}
- previous_state = current_state;
}
-static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick)
+static void UpdateN3DSReleasedButtons(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_state, u32 current_state)
{
- static u32 previous_state = 0;
- u32 updated_up;
- u32 current_state = hidKeysUp();
- updated_up = previous_state ^ current_state;
+ const u32 updated_up = (previous_state ^ current_state) & ~N3DS_HAT_MASK;
if (updated_up) {
- for (Uint8 i = 0; i < joystick->nbuttons; i++) {
- if (current_state & BIT(i) & updated_up) {
- SDL_SendJoystickButton(timestamp, joystick, i, false);
- }
+ Uint8 buttonidx = 0;
+ Uint8 i = 0;
+ while (i < joystick->nbuttons) {
+ if ((buttonidx < 4) || (buttonidx > 7)) { // skip dpad (we treat it as a hat).
+ if (current_state & BIT(buttonidx) & updated_up) {
+ SDL_SendJoystickButton(timestamp, joystick, i, false);
+ }
+ i++;
+ }
+ buttonidx++;
}
}
- previous_state = current_state;
}
+static void UpdateN3DSHat(Uint64 timestamp, SDL_Joystick *joystick, u32 previous_down_state, u32 current_down_state, u32 previous_up_state, u32 current_up_state)
+{
+ // The 3DS dpad looks like 4 buttons at this level, but we treat it as a hat switch, so apps that are talking to SDL_Joystick can hope to do basic directional things without a configuration step.
+ // (but they should _really_ be using the gamepad API.)
+ const u32 updated_hat = (((previous_up_state ^ current_up_state) | (previous_down_state ^ current_down_state)) & N3DS_HAT_MASK); // did bits 4 through 7 change?
+ if (updated_hat) {
+ Uint8 hat = SDL_HAT_CENTERED;
+
+ #define HATSTATE(n3dsbit, sdlenum) if (current_down_state & BIT(n3dsbit)) { hat |= SDL_HAT_##sdlenum; }
+ HATSTATE(4, RIGHT);
+ HATSTATE(5, LEFT);
+ HATSTATE(6, UP);
+ HATSTATE(7, DOWN);
+ #undef HATSTATE
+
+ // this is a physical d-pad on the device, so it probably _can't_ send opposing buttons at the same time, but just in case, cancel them out.
+ if ((hat & (SDL_HAT_UP|SDL_HAT_DOWN)) == (SDL_HAT_UP|SDL_HAT_DOWN)) {
+ hat &= ~(SDL_HAT_UP|SDL_HAT_DOWN);
+ }
+ if ((hat & (SDL_HAT_LEFT|SDL_HAT_RIGHT)) == (SDL_HAT_LEFT|SDL_HAT_RIGHT)) {
+ hat &= ~(SDL_HAT_LEFT|SDL_HAT_RIGHT);
+ }
+ SDL_SendJoystickHat(timestamp, joystick, 0, hat);
+ }
+}
+
+
static void UpdateN3DSCircle(Uint64 timestamp, SDL_Joystick *joystick)
{
static circlePosition previous_state = { 0, 0 };
@@ -194,19 +235,19 @@ static bool N3DS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
*out = (SDL_GamepadMapping){
.a = { EMappingKind_Button, 0 },
.b = { EMappingKind_Button, 1 },
- .x = { EMappingKind_Button, 10 },
- .y = { EMappingKind_Button, 11 },
+ .x = { EMappingKind_Button, 6 },
+ .y = { EMappingKind_Button, 7 },
.back = { EMappingKind_Button, 2 },
.guide = { EMappingKind_None, 255 },
.start = { EMappingKind_Button, 3 },
.leftstick = { EMappingKind_None, 255 },
.rightstick = { EMappingKind_None, 255 },
- .leftshoulder = { EMappingKind_Button, 9 },
- .rightshoulder = { EMappingKind_Button, 8 },
- .dpup = { EMappingKind_Button, 6 },
- .dpdown = { EMappingKind_Button, 7 },
- .dpleft = { EMappingKind_Button, 5 },
- .dpright = { EMappingKind_Button, 4 },
+ .leftshoulder = { EMappingKind_Button, 5 },
+ .rightshoulder = { EMappingKind_Button, 4 },
+ .dpup = { EMappingKind_Hat, SDL_HAT_UP },
+ .dpdown = { EMappingKind_Hat, SDL_HAT_DOWN },
+ .dpleft = { EMappingKind_Hat, SDL_HAT_LEFT },
+ .dpright = { EMappingKind_Hat, SDL_HAT_RIGHT },
.misc1 = { EMappingKind_None, 255 },
.right_paddle1 = { EMappingKind_None, 255 },
.left_paddle1 = { EMappingKind_None, 255 },
@@ -216,8 +257,8 @@ static bool N3DS_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
.lefty = { EMappingKind_Axis, 1 },
.rightx = { EMappingKind_Axis, 2 },
.righty = { EMappingKind_Axis, 3 },
- .lefttrigger = { EMappingKind_Button, 14 },
- .righttrigger = { EMappingKind_Button, 15 },
+ .lefttrigger = { EMappingKind_Button, 10 },
+ .righttrigger = { EMappingKind_Button, 11 },
};
return true;
}