diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 4afeec200..d5b78b29d 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -274,8 +274,6 @@ add_library(core STATIC
     hle/service/mvd/mvd.h
     hle/service/mvd/mvd_std.cpp
     hle/service/mvd/mvd_std.h
-    hle/service/ndm/ndm.cpp
-    hle/service/ndm/ndm.h
     hle/service/ndm/ndm_u.cpp
     hle/service/ndm/ndm_u.h
     hle/service/news/news.cpp
diff --git a/src/core/hle/service/ndm/ndm.cpp b/src/core/hle/service/ndm/ndm.cpp
deleted file mode 100644
index 8a5647072..000000000
--- a/src/core/hle/service/ndm/ndm.cpp
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-#include "common/common_types.h"
-#include "common/logging/log.h"
-#include "core/hle/ipc.h"
-#include "core/hle/service/ndm/ndm.h"
-#include "core/hle/service/ndm/ndm_u.h"
-#include "core/hle/service/service.h"
-
-namespace Service {
-namespace NDM {
-
-enum : u32 {
-    DEFAULT_RETRY_INTERVAL = 10,
-    DEFAULT_SCAN_INTERVAL = 30,
-};
-
-static DaemonMask daemon_bit_mask = DaemonMask::Default;
-static DaemonMask default_daemon_bit_mask = DaemonMask::Default;
-static std::array<DaemonStatus, 4> daemon_status = {
-    DaemonStatus::Idle,
-    DaemonStatus::Idle,
-    DaemonStatus::Idle,
-    DaemonStatus::Idle,
-};
-static ExclusiveState exclusive_state = ExclusiveState::None;
-static u32 scan_interval = DEFAULT_SCAN_INTERVAL;
-static u32 retry_interval = DEFAULT_RETRY_INTERVAL;
-static bool daemon_lock_enabled = false;
-
-void EnterExclusiveState(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    exclusive_state = static_cast<ExclusiveState>(cmd_buff[1]);
-
-    cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) exclusive_state=0x%08X", static_cast<u32>(exclusive_state));
-}
-
-void LeaveExclusiveState(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    exclusive_state = ExclusiveState::None;
-
-    cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) exclusive_state=0x%08X", static_cast<u32>(exclusive_state));
-}
-
-void QueryExclusiveMode(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0x3, 2, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    cmd_buff[2] = static_cast<u32>(exclusive_state);
-    LOG_WARNING(Service_NDM, "(STUBBED) exclusive_state=0x%08X", static_cast<u32>(exclusive_state));
-}
-
-void LockState(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    daemon_lock_enabled = true;
-
-    cmd_buff[0] = IPC::MakeHeader(0x4, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) called");
-}
-
-void UnlockState(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    daemon_lock_enabled = false;
-
-    cmd_buff[0] = IPC::MakeHeader(0x5, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) called");
-}
-
-void SuspendDaemons(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    u32 bit_mask = cmd_buff[1] & 0xF;
-    daemon_bit_mask =
-        static_cast<DaemonMask>(static_cast<u32>(default_daemon_bit_mask) & ~bit_mask);
-    for (size_t index = 0; index < daemon_status.size(); ++index) {
-        if (bit_mask & (1 << index)) {
-            daemon_status[index] = DaemonStatus::Suspended;
-        }
-    }
-
-    cmd_buff[0] = IPC::MakeHeader(0x6, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) daemon_bit_mask=0x%08X", static_cast<u32>(daemon_bit_mask));
-}
-
-void ResumeDaemons(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    u32 bit_mask = cmd_buff[1] & 0xF;
-    daemon_bit_mask = static_cast<DaemonMask>(static_cast<u32>(daemon_bit_mask) | bit_mask);
-    for (size_t index = 0; index < daemon_status.size(); ++index) {
-        if (bit_mask & (1 << index)) {
-            daemon_status[index] = DaemonStatus::Idle;
-        }
-    }
-
-    cmd_buff[0] = IPC::MakeHeader(0x7, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) daemon_bit_mask=0x%08X", static_cast<u32>(daemon_bit_mask));
-}
-
-void SuspendScheduler(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0x8, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) called");
-}
-
-void ResumeScheduler(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) called");
-}
-
-void QueryStatus(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    u32 daemon = cmd_buff[1] & 0xF;
-
-    cmd_buff[0] = IPC::MakeHeader(0xD, 2, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    cmd_buff[2] = static_cast<u32>(daemon_status.at(daemon));
-    LOG_WARNING(Service_NDM, "(STUBBED) daemon=0x%08X, daemon_status=0x%08X", daemon, cmd_buff[2]);
-}
-
-void GetDaemonDisableCount(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    u32 daemon = cmd_buff[1] & 0xF;
-
-    cmd_buff[0] = IPC::MakeHeader(0xE, 3, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    cmd_buff[2] = 0;
-    cmd_buff[3] = 0;
-    LOG_WARNING(Service_NDM, "(STUBBED) daemon=0x%08X", daemon);
-}
-
-void GetSchedulerDisableCount(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0xF, 3, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    cmd_buff[2] = 0;
-    cmd_buff[3] = 0;
-    LOG_WARNING(Service_NDM, "(STUBBED) called");
-}
-
-void SetScanInterval(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    scan_interval = cmd_buff[1];
-
-    cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) scan_interval=0x%08X", scan_interval);
-}
-
-void GetScanInterval(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0x11, 2, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    cmd_buff[2] = scan_interval;
-    LOG_WARNING(Service_NDM, "(STUBBED) scan_interval=0x%08X", scan_interval);
-}
-
-void SetRetryInterval(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    retry_interval = cmd_buff[1];
-
-    cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) retry_interval=0x%08X", retry_interval);
-}
-
-void GetRetryInterval(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0x13, 2, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    cmd_buff[2] = retry_interval;
-    LOG_WARNING(Service_NDM, "(STUBBED) retry_interval=0x%08X", retry_interval);
-}
-
-void OverrideDefaultDaemons(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    u32 bit_mask = cmd_buff[1] & 0xF;
-    default_daemon_bit_mask = static_cast<DaemonMask>(bit_mask);
-    daemon_bit_mask = default_daemon_bit_mask;
-    for (size_t index = 0; index < daemon_status.size(); ++index) {
-        if (bit_mask & (1 << index)) {
-            daemon_status[index] = DaemonStatus::Idle;
-        }
-    }
-
-    cmd_buff[0] = IPC::MakeHeader(0x14, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) default_daemon_bit_mask=0x%08X ",
-                static_cast<u32>(default_daemon_bit_mask));
-}
-
-void ResetDefaultDaemons(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-    default_daemon_bit_mask = DaemonMask::Default;
-
-    cmd_buff[0] = IPC::MakeHeader(0x15, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) default_daemon_bit_mask=0x%08X",
-                static_cast<u32>(default_daemon_bit_mask));
-}
-
-void GetDefaultDaemons(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0x16, 2, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    cmd_buff[2] = static_cast<u32>(default_daemon_bit_mask);
-    LOG_WARNING(Service_NDM, "(STUBBED) default_daemon_bit_mask=0x%08X",
-                static_cast<u32>(default_daemon_bit_mask));
-}
-
-void ClearHalfAwakeMacFilter(Service::Interface* self) {
-    u32* cmd_buff = Kernel::GetCommandBuffer();
-
-    cmd_buff[0] = IPC::MakeHeader(0x17, 1, 0);
-    cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-    LOG_WARNING(Service_NDM, "(STUBBED) called");
-}
-
-void Init() {
-    AddService(new NDM_U_Interface);
-}
-
-void Shutdown() {}
-
-} // namespace NDM
-} // namespace Service
diff --git a/src/core/hle/service/ndm/ndm.h b/src/core/hle/service/ndm/ndm.h
deleted file mode 100644
index 979e7fcf1..000000000
--- a/src/core/hle/service/ndm/ndm.h
+++ /dev/null
@@ -1,251 +0,0 @@
-// Copyright 2016 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/common_types.h"
-
-namespace Service {
-
-class Interface;
-
-namespace NDM {
-
-enum class Daemon : u32 {
-    Cec = 0,
-    Boss = 1,
-    Nim = 2,
-    Friend = 3,
-};
-
-enum class DaemonMask : u32 {
-    None = 0,
-    Cec = (1 << static_cast<u32>(Daemon::Cec)),
-    Boss = (1 << static_cast<u32>(Daemon::Boss)),
-    Nim = (1 << static_cast<u32>(Daemon::Nim)),
-    Friend = (1 << static_cast<u32>(Daemon::Friend)),
-    Default = Cec | Friend,
-    All = Cec | Boss | Nim | Friend,
-};
-
-enum class DaemonStatus : u32 { Busy = 0, Idle = 1, Suspending = 2, Suspended = 3 };
-
-enum class ExclusiveState : u32 {
-    None = 0,
-    Infrastructure = 1,
-    LocalCommunications = 2,
-    Streetpass = 3,
-    StreetpassData = 4,
-};
-
-/**
- *  NDM::EnterExclusiveState service function
- *  Inputs:
- *      0 : Header code [0x00010042]
- *      1 : Exclusive State
- *      2 : 0x20
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void EnterExclusiveState(Service::Interface* self);
-
-/**
- *  NDM::LeaveExclusiveState service function
- *  Inputs:
- *      0 : Header code [0x00020002]
- *      1 : 0x20
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void LeaveExclusiveState(Service::Interface* self);
-
-/**
- *  NDM::QueryExclusiveMode service function
- *  Inputs:
- *      0 : Header code [0x00030000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Current Exclusive State
- */
-void QueryExclusiveMode(Service::Interface* self);
-
-/**
- *  NDM::LockState service function
- *  Inputs:
- *      0 : Header code [0x00040002]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void LockState(Service::Interface* self);
-
-/**
- *  NDM::UnlockState service function
- *  Inputs:
- *      0 : Header code [0x00050002]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void UnlockState(Service::Interface* self);
-
-/**
- *  NDM::SuspendDaemons service function
- *  Inputs:
- *      0 : Header code [0x00060040]
- *      1 : Daemon bit mask
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void SuspendDaemons(Service::Interface* self);
-
-/**
- *  NDM::ResumeDaemons service function
- *  Inputs:
- *      0 : Header code [0x00070040]
- *      1 : Daemon bit mask
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void ResumeDaemons(Service::Interface* self);
-
-/**
- *  NDM::SuspendScheduler service function
- *  Inputs:
- *      0 : Header code [0x00080040]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void SuspendScheduler(Service::Interface* self);
-
-/**
- *  NDM::ResumeScheduler service function
- *  Inputs:
- *      0 : Header code [0x00090000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void ResumeScheduler(Service::Interface* self);
-
-/**
- *  NDM::QueryStatus service function
- *  Inputs:
- *      0 : Header code [0x000D0040]
- *      1 : Daemon
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Daemon status
- */
-void QueryStatus(Service::Interface* self);
-
-/**
- *  NDM::GetDaemonDisableCount service function
- *  Inputs:
- *      0 : Header code [0x000E0040]
- *      1 : Daemon
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Current process disable count
- *      3 : Total disable count
- */
-void GetDaemonDisableCount(Service::Interface* self);
-
-/**
- *  NDM::GetSchedulerDisableCount service function
- *  Inputs:
- *      0 : Header code [0x000F0000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Current process disable count
- *      3 : Total disable count
- */
-void GetSchedulerDisableCount(Service::Interface* self);
-
-/**
- *  NDM::SetScanInterval service function
- *  Inputs:
- *      0 : Header code [0x00100040]
- *      1 : Interval (default = 30)
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void SetScanInterval(Service::Interface* self);
-
-/**
- *  NDM::GetScanInterval service function
- *  Inputs:
- *      0 : Header code [0x00110000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Interval (default = 30)
- */
-void GetScanInterval(Service::Interface* self);
-
-/**
- *  NDM::SetRetryInterval service function
- *  Inputs:
- *      0 : Header code [0x00120040]
- *      1 : Interval (default = 10)
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void SetRetryInterval(Service::Interface* self);
-
-/**
- *  NDM::GetRetryInterval service function
- *  Inputs:
- *      0 : Header code [0x00130000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Interval (default = 10)
- */
-void GetRetryInterval(Service::Interface* self);
-
-/**
- *  NDM::OverrideDefaultDaemons service function
- *  Inputs:
- *      0 : Header code [0x00140040]
- *      1 : Daemon bit mask
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void OverrideDefaultDaemons(Service::Interface* self);
-
-/**
- *  NDM::ResetDefaultDaemons service function
- *  Inputs:
- *      0 : Header code [0x00150000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void ResetDefaultDaemons(Service::Interface* self);
-
-/**
- *  NDM::GetDefaultDaemons service function
- *  Inputs:
- *      0 : Header code [0x00160000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Daemon bit mask
- *  Note:
- *      Gets the current default daemon bit mask. The default value is (DAEMONMASK_CEC |
- * DAEMONMASK_FRIENDS)
- */
-void GetDefaultDaemons(Service::Interface* self);
-
-/**
- *  NDM::ClearHalfAwakeMacFilter service function
- *  Inputs:
- *      0 : Header code [0x00170000]
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void ClearHalfAwakeMacFilter(Service::Interface* self);
-
-/// Initialize NDM service
-void Init();
-
-/// Shutdown NDM service
-void Shutdown();
-
-} // namespace NDM
-} // namespace Service
diff --git a/src/core/hle/service/ndm/ndm_u.cpp b/src/core/hle/service/ndm/ndm_u.cpp
index f5c7a341a..7afea5399 100644
--- a/src/core/hle/service/ndm/ndm_u.cpp
+++ b/src/core/hle/service/ndm/ndm_u.cpp
@@ -2,40 +2,243 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include "core/hle/service/ndm/ndm.h"
+#include "core/hle/ipc_helpers.h"
 #include "core/hle/service/ndm/ndm_u.h"
 
 namespace Service {
 namespace NDM {
 
-const Interface::FunctionInfo FunctionTable[] = {
-    {0x00010042, EnterExclusiveState, "EnterExclusiveState"},
-    {0x00020002, LeaveExclusiveState, "LeaveExclusiveState"},
-    {0x00030000, QueryExclusiveMode, "QueryExclusiveMode"},
-    {0x00040002, LockState, "LockState"},
-    {0x00050002, UnlockState, "UnlockState"},
-    {0x00060040, SuspendDaemons, "SuspendDaemons"},
-    {0x00070040, ResumeDaemons, "ResumeDaemons"},
-    {0x00080040, SuspendScheduler, "SuspendScheduler"},
-    {0x00090000, ResumeScheduler, "ResumeScheduler"},
-    {0x000A0000, nullptr, "GetCurrentState"},
-    {0x000B0000, nullptr, "GetTargetState"},
-    {0x000C0000, nullptr, "<Stubbed>"},
-    {0x000D0040, QueryStatus, "QueryStatus"},
-    {0x000E0040, GetDaemonDisableCount, "GetDaemonDisableCount"},
-    {0x000F0000, GetSchedulerDisableCount, "GetSchedulerDisableCount"},
-    {0x00100040, SetScanInterval, "SetScanInterval"},
-    {0x00110000, GetScanInterval, "GetScanInterval"},
-    {0x00120040, SetRetryInterval, "SetRetryInterval"},
-    {0x00130000, GetRetryInterval, "GetRetryInterval"},
-    {0x00140040, OverrideDefaultDaemons, "OverrideDefaultDaemons"},
-    {0x00150000, ResetDefaultDaemons, "ResetDefaultDaemons"},
-    {0x00160000, GetDefaultDaemons, "GetDefaultDaemons"},
-    {0x00170000, ClearHalfAwakeMacFilter, "ClearHalfAwakeMacFilter"},
-};
+void NDM_U::EnterExclusiveState(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x01, 1, 2);
+    exclusive_state = rp.PopEnum<ExclusiveState>();
+    rp.PopPID();
 
-NDM_U_Interface::NDM_U_Interface() {
-    Register(FunctionTable);
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED) exclusive_state=0x{:08X}",
+                  static_cast<u32>(exclusive_state));
+}
+
+void NDM_U::LeaveExclusiveState(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x02, 0, 2);
+    rp.PopPID();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::QueryExclusiveMode(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x03, 0, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.PushEnum(exclusive_state);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::LockState(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x04, 0, 2);
+    rp.PopPID();
+    daemon_lock_enabled = true;
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::UnlockState(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x05, 0, 2);
+    rp.PopPID();
+    daemon_lock_enabled = false;
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::SuspendDaemons(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x06, 1, 0);
+    u32 bit_mask = rp.Pop<u32>() & 0xF;
+    daemon_bit_mask =
+        static_cast<DaemonMask>(static_cast<u32>(default_daemon_bit_mask) & ~bit_mask);
+    for (std::size_t index = 0; index < daemon_status.size(); ++index) {
+        if (bit_mask & (1 << index)) {
+            daemon_status[index] = DaemonStatus::Suspended;
+        }
+    }
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED) bit_mask=0x{:08X}", bit_mask);
+}
+
+void NDM_U::ResumeDaemons(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x07, 1, 0);
+    u32 bit_mask = rp.Pop<u32>() & 0xF;
+    daemon_bit_mask = static_cast<DaemonMask>(static_cast<u32>(daemon_bit_mask) & ~bit_mask);
+    for (std::size_t index = 0; index < daemon_status.size(); ++index) {
+        if (bit_mask & (1 << index)) {
+            daemon_status[index] = DaemonStatus::Idle;
+        }
+    }
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED) bit_mask=0x{:08X}", bit_mask);
+}
+
+void NDM_U::SuspendScheduler(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x08, 1, 0);
+    bool perform_in_background = rp.Pop<bool>();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED) perform_in_background={}", perform_in_background);
+}
+
+void NDM_U::ResumeScheduler(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x09, 0, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::QueryStatus(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0D, 1, 0);
+    u8 daemon = rp.Pop<u8>();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.PushEnum(daemon_status.at(daemon));
+    NGLOG_WARNING(Service_NDM, "(STUBBED) daemon=0x{:02X}", daemon);
+}
+
+void NDM_U::GetDaemonDisableCount(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0E, 1, 0);
+    u8 daemon = rp.Pop<u8>();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.Push<u32>(0); // current process disable count
+    rb.Push<u32>(0); // total disable count
+    NGLOG_WARNING(Service_NDM, "(STUBBED) daemon=0x{:02X}", daemon);
+}
+
+void NDM_U::GetSchedulerDisableCount(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0F, 0, 0);
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(3, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.Push<u32>(0); // current process disable count
+    rb.Push<u32>(0); // total disable count
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::SetScanInterval(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x10, 1, 0);
+    scan_interval = rp.Pop<u32>();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED) scan_interval=0x{:08X}", scan_interval);
+}
+
+void NDM_U::GetScanInterval(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x11, 0, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.Push(scan_interval);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::SetRetryInterval(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x12, 1, 0);
+    retry_interval = rp.Pop<u32>();
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED) retry_interval=0x{:08X}", retry_interval);
+}
+
+void NDM_U::GetRetryInterval(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x13, 0, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.Push(retry_interval);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::OverrideDefaultDaemons(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x14, 1, 0);
+    u32 bit_mask = rp.Pop<u32>() & 0xF;
+    default_daemon_bit_mask = static_cast<DaemonMask>(bit_mask);
+    daemon_bit_mask = default_daemon_bit_mask;
+    for (std::size_t index = 0; index < daemon_status.size(); ++index) {
+        if (bit_mask & (1 << index)) {
+            daemon_status[index] = DaemonStatus::Idle;
+        }
+    }
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED) bit_mask=0x{:08X}", bit_mask);
+}
+
+void NDM_U::ResetDefaultDaemons(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x15, 0, 0);
+    default_daemon_bit_mask = DaemonMask::Default;
+
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::GetDefaultDaemons(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x16, 0, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    rb.Push(RESULT_SUCCESS);
+    rb.PushEnum(default_daemon_bit_mask);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+void NDM_U::ClearHalfAwakeMacFilter(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x17, 0, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    rb.Push(RESULT_SUCCESS);
+    NGLOG_WARNING(Service_NDM, "(STUBBED)");
+}
+
+NDM_U::NDM_U() : ServiceFramework("ndm:u", 6) {
+    static const FunctionInfo functions[] = {
+        {0x00010042, &NDM_U::EnterExclusiveState, "EnterExclusiveState"},
+        {0x00020002, &NDM_U::LeaveExclusiveState, "LeaveExclusiveState"},
+        {0x00030000, &NDM_U::QueryExclusiveMode, "QueryExclusiveMode"},
+        {0x00040002, &NDM_U::LockState, "LockState"},
+        {0x00050002, &NDM_U::UnlockState, "UnlockState"},
+        {0x00060040, &NDM_U::SuspendDaemons, "SuspendDaemons"},
+        {0x00070040, &NDM_U::ResumeDaemons, "ResumeDaemons"},
+        {0x00080040, &NDM_U::SuspendScheduler, "SuspendScheduler"},
+        {0x00090000, &NDM_U::ResumeScheduler, "ResumeScheduler"},
+        {0x000A0000, nullptr, "GetCurrentState"},
+        {0x000B0000, nullptr, "GetTargetState"},
+        {0x000C0000, nullptr, "<Stubbed>"},
+        {0x000D0040, &NDM_U::QueryStatus, "QueryStatus"},
+        {0x000E0040, &NDM_U::GetDaemonDisableCount, "GetDaemonDisableCount"},
+        {0x000F0000, &NDM_U::GetSchedulerDisableCount, "GetSchedulerDisableCount"},
+        {0x00100040, &NDM_U::SetScanInterval, "SetScanInterval"},
+        {0x00110000, &NDM_U::GetScanInterval, "GetScanInterval"},
+        {0x00120040, &NDM_U::SetRetryInterval, "SetRetryInterval"},
+        {0x00130000, &NDM_U::GetRetryInterval, "GetRetryInterval"},
+        {0x00140040, &NDM_U::OverrideDefaultDaemons, "OverrideDefaultDaemons"},
+        {0x00150000, &NDM_U::ResetDefaultDaemons, "ResetDefaultDaemons"},
+        {0x00160000, &NDM_U::GetDefaultDaemons, "GetDefaultDaemons"},
+        {0x00170000, &NDM_U::ClearHalfAwakeMacFilter, "ClearHalfAwakeMacFilter"},
+    };
+    RegisterHandlers(functions);
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+    std::make_shared<NDM_U>()->InstallAsService(service_manager);
 }
 
 } // namespace NDM
diff --git a/src/core/hle/service/ndm/ndm_u.h b/src/core/hle/service/ndm/ndm_u.h
index d567abc84..cfac12620 100644
--- a/src/core/hle/service/ndm/ndm_u.h
+++ b/src/core/hle/service/ndm/ndm_u.h
@@ -4,19 +4,272 @@
 
 #pragma once
 
+#include <array>
 #include "core/hle/service/service.h"
 
 namespace Service {
 namespace NDM {
 
-class NDM_U_Interface : public Service::Interface {
+class NDM_U final : public ServiceFramework<NDM_U> {
 public:
-    NDM_U_Interface();
+    NDM_U();
 
-    std::string GetPortName() const override {
-        return "ndm:u";
-    }
+private:
+    /**
+     *  NDM::EnterExclusiveState service function
+     *  Inputs:
+     *      0 : Header code [0x00010042]
+     *      1 : Exclusive State
+     *      2 : 0x20
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void EnterExclusiveState(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::LeaveExclusiveState service function
+     *  Inputs:
+     *      0 : Header code [0x00020002]
+     *      1 : 0x20
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void LeaveExclusiveState(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::QueryExclusiveMode service function
+     *  Inputs:
+     *      0 : Header code [0x00030000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     *      2 : Current Exclusive State
+     */
+    void QueryExclusiveMode(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::LockState service function
+     *  Inputs:
+     *      0 : Header code [0x00040002]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void LockState(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::UnlockState service function
+     *  Inputs:
+     *      0 : Header code [0x00050002]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void UnlockState(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::SuspendDaemons service function
+     *  Inputs:
+     *      0 : Header code [0x00060040]
+     *      1 : Daemon bit mask
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void SuspendDaemons(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::ResumeDaemons service function
+     *  Inputs:
+     *      0 : Header code [0x00070040]
+     *      1 : Daemon bit mask
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void ResumeDaemons(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::SuspendScheduler service function
+     *  Inputs:
+     *      0 : Header code [0x00080040]
+     *      1 : (u8/bool) 0 = Wait for completion, 1 = Perform in background
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void SuspendScheduler(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::ResumeScheduler service function
+     *  Inputs:
+     *      0 : Header code [0x00090000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void ResumeScheduler(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::QueryStatus service function
+     *  Inputs:
+     *      0 : Header code [0x000D0040]
+     *      1 : Daemon
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     *      2 : Daemon status
+     */
+    void QueryStatus(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::GetDaemonDisableCount service function
+     *  Inputs:
+     *      0 : Header code [0x000E0040]
+     *      1 : Daemon
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     *      2 : Current process disable count
+     *      3 : Total disable count
+     */
+    void GetDaemonDisableCount(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::GetSchedulerDisableCount service function
+     *  Inputs:
+     *      0 : Header code [0x000F0000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     *      2 : Current process disable count
+     *      3 : Total disable count
+     */
+    void GetSchedulerDisableCount(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::SetScanInterval service function
+     *  Inputs:
+     *      0 : Header code [0x00100040]
+     *      1 : Interval (default = 30)
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void SetScanInterval(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::GetScanInterval service function
+     *  Inputs:
+     *      0 : Header code [0x00110000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     *      2 : Interval (default = 30)
+     */
+    void GetScanInterval(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::SetRetryInterval service function
+     *  Inputs:
+     *      0 : Header code [0x00120040]
+     *      1 : Interval (default = 10)
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void SetRetryInterval(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::GetRetryInterval service function
+     *  Inputs:
+     *      0 : Header code [0x00130000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     *      2 : Interval (default = 10)
+     */
+    void GetRetryInterval(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::OverrideDefaultDaemons service function
+     *  Inputs:
+     *      0 : Header code [0x00140040]
+     *      1 : Daemon bit mask
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void OverrideDefaultDaemons(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::ResetDefaultDaemons service function
+     *  Inputs:
+     *      0 : Header code [0x00150000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void ResetDefaultDaemons(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::GetDefaultDaemons service function
+     *  Inputs:
+     *      0 : Header code [0x00160000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     *      2 : Daemon bit mask
+     *  Note:
+     *      Gets the current default daemon bit mask. The default value is (DAEMONMASK_CEC |
+     * DAEMONMASK_FRIENDS)
+     */
+    void GetDefaultDaemons(Kernel::HLERequestContext& ctx);
+
+    /**
+     *  NDM::ClearHalfAwakeMacFilter service function
+     *  Inputs:
+     *      0 : Header code [0x00170000]
+     *  Outputs:
+     *      1 : Result, 0 on success, otherwise error code
+     */
+    void ClearHalfAwakeMacFilter(Kernel::HLERequestContext& ctx);
+
+    enum class Daemon : u32 {
+        Cec = 0,
+        Boss = 1,
+        Nim = 2,
+        Friend = 3,
+    };
+
+    enum class DaemonMask : u32 {
+        None = 0,
+        Cec = (1 << static_cast<u32>(Daemon::Cec)),
+        Boss = (1 << static_cast<u32>(Daemon::Boss)),
+        Nim = (1 << static_cast<u32>(Daemon::Nim)),
+        Friend = (1 << static_cast<u32>(Daemon::Friend)),
+        Default = Cec | Friend,
+        All = Cec | Boss | Nim | Friend,
+    };
+
+    enum class DaemonStatus : u32 {
+        Busy = 0,
+        Idle = 1,
+        Suspending = 2,
+        Suspended = 3,
+    };
+
+    enum class ExclusiveState : u32 {
+        None = 0,
+        Infrastructure = 1,
+        LocalCommunications = 2,
+        Streetpass = 3,
+        StreetpassData = 4,
+    };
+
+    enum : u32 {
+        DEFAULT_RETRY_INTERVAL = 10,
+        DEFAULT_SCAN_INTERVAL = 30,
+    };
+
+    DaemonMask daemon_bit_mask = DaemonMask::Default;
+    DaemonMask default_daemon_bit_mask = DaemonMask::Default;
+    std::array<DaemonStatus, 4> daemon_status = {
+        DaemonStatus::Idle,
+        DaemonStatus::Idle,
+        DaemonStatus::Idle,
+        DaemonStatus::Idle,
+    };
+    ExclusiveState exclusive_state = ExclusiveState::None;
+    u32 scan_interval = DEFAULT_SCAN_INTERVAL;
+    u32 retry_interval = DEFAULT_RETRY_INTERVAL;
+    bool daemon_lock_enabled = false;
 };
 
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
 } // namespace NDM
 } // namespace Service
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 813893985..55eb8e5eb 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -37,7 +37,7 @@
 #include "core/hle/service/ldr_ro/ldr_ro.h"
 #include "core/hle/service/mic_u.h"
 #include "core/hle/service/mvd/mvd.h"
-#include "core/hle/service/ndm/ndm.h"
+#include "core/hle/service/ndm/ndm_u.h"
 #include "core/hle/service/news/news.h"
 #include "core/hle/service/nfc/nfc.h"
 #include "core/hle/service/nim/nim.h"
@@ -250,7 +250,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
     HID::InstallInterfaces(*sm);
     IR::InstallInterfaces(*sm);
     MVD::Init();
-    NDM::Init();
+    NDM::InstallInterfaces(*sm);
     NEWS::InstallInterfaces(*sm);
     NFC::InstallInterfaces(*sm);
     NIM::InstallInterfaces(*sm);
@@ -272,7 +272,6 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
 
 /// Shutdown ServiceManager
 void Shutdown() {
-    NDM::Shutdown();
     DLP::Shutdown();
     CECD::Shutdown();
     BOSS::Shutdown();