diff --git a/nx/include/switch/services/hid.h b/nx/include/switch/services/hid.h
index c85e13ca..b179a4a0 100644
--- a/nx/include/switch/services/hid.h
+++ b/nx/include/switch/services/hid.h
@@ -545,8 +545,8 @@ typedef struct HidControllerMAC {
     u64 timestamp_2;
 } HidControllerMAC;
 
-/// HidControllerHeader
-typedef struct HidControllerHeader {
+/// HidNpadStateHeader
+typedef struct HidNpadStateHeader {
     u32 type;
     u32 isHalf;
     u32 singleColorsDescriptor;
@@ -557,9 +557,9 @@ typedef struct HidControllerHeader {
     u32 leftColorButtons;
     u32 rightColorBody;
     u32 rightColorButtons;
-} HidControllerHeader;
+} HidNpadStateHeader;
 
-/// Info struct extracted from HidControllerHeader.
+/// Info struct extracted from HidNpadStateHeader.
 /// Color fields are zero when not set. This can happen even when the *Set fields are set to true.
 typedef struct HidControllerColors
 {
@@ -576,19 +576,30 @@ typedef struct HidControllerColors
 
 /// HidControllerLayoutHeader
 typedef struct HidControllerLayoutHeader {
-    u64 timestampTicks;
-    u64 numEntries;
-    u64 latestEntry;
-    u64 maxEntryIndex;
+    u64 timestamp_ticks;
+    u64 total_entries;
+    u64 latest_entry;
+    u64 max_entry;
 } HidControllerLayoutHeader;
 
+/// HidNpadStateEntry
+typedef struct HidNpadStateEntry {
+    u64 timestamp;
+    u64 buttons;
+    JoystickPosition joysticks[JOYSTICK_NUM_STICKS];
+    u64 connectionState;
+} HidNpadStateEntry;
+
+typedef HidNpadStateEntry HidNpadFullKeyState;
+typedef HidNpadStateEntry HidNpadHandheldState;
+typedef HidNpadStateEntry HidNpadJoyDualState;
+typedef HidNpadStateEntry HidNpadJoyLeftState;
+typedef HidNpadStateEntry HidNpadJoyRightState;
+
 /// HidControllerInputEntry
 typedef struct HidControllerInputEntry {
     u64 timestamp;
-    u64 timestamp_2;
-    u64 buttons;
-    JoystickPosition joysticks[JOYSTICK_NUM_STICKS];
-    u64 connectionState;
+    HidNpadStateEntry state;
 } HidControllerInputEntry;
 
 /// HidControllerLayout
@@ -661,7 +672,7 @@ typedef struct {
 
 /// HidController
 typedef struct HidController {
-    HidControllerHeader header;
+    HidNpadStateHeader header;
     HidControllerLayout layouts[7];
     HidControllerSixAxisLayout sixaxis[6];
     HidControllerMisc misc;
@@ -788,6 +799,12 @@ void hidGetControllerPowerInfo(HidControllerID id, HidPowerInfo *info, size_t to
 
 void hidScanInput(void);
 
+void hidGetNpadStatesFullKey(u32 id, HidNpadFullKeyState *states, size_t count, size_t *total_out);
+void hidGetNpadStatesHandheld(u32 id, HidNpadHandheldState *states, size_t count, size_t *total_out);
+void hidGetNpadStatesJoyDual(u32 id, HidNpadJoyDualState *states, size_t count, size_t *total_out);
+void hidGetNpadStatesJoyLeft(u32 id, HidNpadJoyLeftState *states, size_t count, size_t *total_out);
+void hidGetNpadStatesJoyRight(u32 id, HidNpadJoyRightState *states, size_t count, size_t *total_out);
+
 u64 hidKeysHeld(HidControllerID id);
 u64 hidKeysDown(HidControllerID id);
 u64 hidKeysUp(HidControllerID id);
diff --git a/nx/source/services/hid.c b/nx/source/services/hid.c
index 59e68e37..c5043bda 100644
--- a/nx/source/services/hid.c
+++ b/nx/source/services/hid.c
@@ -9,6 +9,7 @@
 #include "services/applet.h"
 #include "services/hid.h"
 #include "runtime/hosversion.h"
+#include "runtime/diag.h"
 
 static Service g_hidSrv;
 static Service g_hidIAppletResource;
@@ -19,7 +20,7 @@ static HidTouchScreenEntry g_touchEntry;
 static HidMouseEntry *g_mouseEntry;
 static HidMouse g_mouse;
 static HidKeyboardEntry g_keyboardEntry;
-static HidControllerHeader g_controllerHeaders[10];
+static HidNpadStateHeader g_controllerHeaders[10];
 static HidControllerInputEntry g_controllerEntries[10];
 static HidControllerSixAxisLayout g_sixaxisLayouts[10];
 static HidControllerMisc g_controllerMisc[10];
@@ -238,14 +239,14 @@ void hidScanInput(void) {
 
     for (int i = 0; i < 10; i++) {
         HidControllerLayout *currentLayout = &sharedMem->controllers[i].layouts[g_controllerLayout[i]];
-        memcpy(&g_controllerHeaders[i], &sharedMem->controllers[i].header, sizeof(HidControllerHeader));
-        u64 latestControllerEntry = currentLayout->header.latestEntry;
+        memcpy(&g_controllerHeaders[i], &sharedMem->controllers[i].header, sizeof(HidNpadStateHeader));
+        u64 latestControllerEntry = currentLayout->header.latest_entry;
         HidControllerInputEntry *newInputEntry = &currentLayout->entries[latestControllerEntry];
         if ((s64)(newInputEntry->timestamp - g_controllerTimestamps[i]) >= 0) {
             memcpy(&g_controllerEntries[i], newInputEntry, sizeof(HidControllerInputEntry));
             g_controllerTimestamps[i] = newInputEntry->timestamp;
 
-            g_controllerHeld[i] |= g_controllerEntries[i].buttons;
+            g_controllerHeld[i] |= g_controllerEntries[i].state.buttons;
         }
 
         g_controllerDown[i] = (~g_controllerOld[i]) & g_controllerHeld[i];
@@ -283,7 +284,7 @@ void hidScanInput(void) {
     }
 
     g_controllerP1AutoID = CONTROLLER_HANDHELD;
-    if (g_controllerEntries[CONTROLLER_PLAYER_1].connectionState & CONTROLLER_STATE_CONNECTED)
+    if (g_controllerEntries[CONTROLLER_PLAYER_1].state.connectionState & CONTROLLER_STATE_CONNECTED)
        g_controllerP1AutoID = CONTROLLER_PLAYER_1;
 
     rwlockWriteUnlock(&g_hidLock);
@@ -308,7 +309,7 @@ void hidGetControllerColors(HidControllerID id, HidControllerColors *colors) {
     if (id < 0 || id > 9) return;
     if (colors == NULL) return;
 
-    HidControllerHeader *hdr = &g_controllerHeaders[id];
+    HidNpadStateHeader *hdr = &g_controllerHeaders[id];
 
     memset(colors, 0, sizeof(HidControllerColors));
 
@@ -338,7 +339,7 @@ bool hidIsControllerConnected(HidControllerID id) {
     if (id < 0 || id > 9) return 0;
 
     rwlockReadLock(&g_hidLock);
-    bool flag = (g_controllerEntries[id].connectionState & CONTROLLER_STATE_CONNECTED) != 0;
+    bool flag = (g_controllerEntries[id].state.connectionState & CONTROLLER_STATE_CONNECTED) != 0;
     rwlockReadUnlock(&g_hidLock);
     return flag;
 }
@@ -394,6 +395,82 @@ void hidGetControllerPowerInfo(HidControllerID id, HidPowerInfo *info, size_t to
     }
 }
 
+static HidController *_hidNpadSharedmemGetInternalState(u32 id) {
+    if (id >= 0x8) id = id==0x10 ? 0x9 : 0x8;
+
+    HidSharedMemory *sharedmem = (HidSharedMemory*)hidGetSharedmemAddr();
+    if (sharedmem == NULL) return NULL;
+    return &sharedmem->controllers[id];
+}
+
+static Result _hidGetNpadStates(u32 id, u32 layout, HidNpadStateEntry *states, size_t count, size_t *total_out) {
+    if (total_out) *total_out = 0;
+
+    if (id >= 0x8 && (id!=0x10 && id!=0x20))
+        return MAKERESULT(Module_Libnx, LibnxError_BadInput);
+
+    HidController *npad = _hidNpadSharedmemGetInternalState(id);
+    if (npad == NULL)
+        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
+
+    HidControllerLayout *states_buf = &npad->layouts[layout];
+
+    s32 total_entries = (s32)atomic_load_explicit(&states_buf->header.max_entry, memory_order_acquire);
+    if (total_entries < 0) total_entries = 0;
+    if (total_entries > count) total_entries = count;
+    s32 latest_entry = (s32)atomic_load_explicit(&states_buf->header.latest_entry, memory_order_acquire);
+
+    for (s32 i=0; i<total_entries; i++) {
+        s32 entrypos = (((latest_entry + 0x12) - total_entries) + i) % 0x11;
+
+        u64 timestamp0=0, timestamp1=0;
+
+        timestamp0 = atomic_load_explicit(&states_buf->entries[entrypos].timestamp, memory_order_acquire);
+        memcpy(&states[total_entries-i-1], &states_buf->entries[entrypos].state, sizeof(HidNpadStateEntry));
+        timestamp1 = atomic_load_explicit(&states_buf->entries[entrypos].timestamp, memory_order_acquire);
+
+        if (timestamp0 != timestamp1 || (i>0 && states[total_entries-i-1].timestamp - states[total_entries-i].timestamp != 1)) {
+            s32 tmpcount = (s32)atomic_load_explicit(&states_buf->header.max_entry, memory_order_acquire);
+            tmpcount = total_entries < tmpcount ? tmpcount : total_entries;
+            total_entries = tmpcount < count ? tmpcount : count;
+            latest_entry = (s32)atomic_load_explicit(&states_buf->header.latest_entry, memory_order_acquire);
+
+            i=-1;
+        }
+    }
+
+    if (total_out) *total_out = total_entries;
+
+    // sdknso would handle button-bitmasking with ControlPadRestriction here.
+
+    return 0;
+}
+
+void hidGetNpadStatesFullKey(u32 id, HidNpadFullKeyState *states, size_t count, size_t *total_out) {
+    Result rc = _hidGetNpadStates(id, 0, states, count, total_out);
+    if (R_FAILED(rc)) diagAbortWithResult(rc);
+}
+
+void hidGetNpadStatesHandheld(u32 id, HidNpadHandheldState *states, size_t count, size_t *total_out) {
+    Result rc = _hidGetNpadStates(id, 1, states, count, total_out);
+    if (R_FAILED(rc)) diagAbortWithResult(rc);
+}
+
+void hidGetNpadStatesJoyDual(u32 id, HidNpadJoyDualState *states, size_t count, size_t *total_out) {
+    Result rc = _hidGetNpadStates(id, 2, states, count, total_out);
+    if (R_FAILED(rc)) diagAbortWithResult(rc);
+}
+
+void hidGetNpadStatesJoyLeft(u32 id, HidNpadJoyLeftState *states, size_t count, size_t *total_out) {
+    Result rc = _hidGetNpadStates(id, 3, states, count, total_out);
+    if (R_FAILED(rc)) diagAbortWithResult(rc);
+}
+
+void hidGetNpadStatesJoyRight(u32 id, HidNpadJoyRightState *states, size_t count, size_t *total_out) {
+    Result rc = _hidGetNpadStates(id, 4, states, count, total_out);
+    if (R_FAILED(rc)) diagAbortWithResult(rc);
+}
+
 u64 hidKeysHeld(HidControllerID id) {
     if (id==CONTROLLER_P1_AUTO) return hidKeysHeld(g_controllerP1AutoID);
     if (id < 0 || id > 9) return 0;
@@ -569,8 +646,8 @@ void hidJoystickRead(JoystickPosition *pos, HidControllerID id, HidControllerJoy
         }
 
         rwlockReadLock(&g_hidLock);
-        pos->dx = g_controllerEntries[id].joysticks[stick].dx;
-        pos->dy = g_controllerEntries[id].joysticks[stick].dy;
+        pos->dx = g_controllerEntries[id].state.joysticks[stick].dx;
+        pos->dy = g_controllerEntries[id].state.joysticks[stick].dy;
         rwlockReadUnlock(&g_hidLock);
     }
 }