diff --git a/Ryujinx.HLE/FileSystem/SaveHelper.cs b/Ryujinx.HLE/FileSystem/SaveHelper.cs
index 0dfcfd2b..411d13e2 100644
--- a/Ryujinx.HLE/FileSystem/SaveHelper.cs
+++ b/Ryujinx.HLE/FileSystem/SaveHelper.cs
@@ -32,7 +32,7 @@ namespace Ryujinx.HLE.FileSystem
                 currentTitleId = context.Process.TitleId;
             }
 
-            string saveAccount = saveMetaData.UserId.IsZero() ? "savecommon" : saveMetaData.UserId.ToString();
+            string saveAccount = saveMetaData.UserId.IsNull ? "savecommon" : saveMetaData.UserId.ToString();
 
             string savePath = Path.Combine(baseSavePath,
                 saveMetaData.SaveId.ToString("x16"),
diff --git a/Ryujinx.HLE/HOS/Services/Friend/FriendErr.cs b/Ryujinx.HLE/HOS/Services/Friend/FriendErr.cs
deleted file mode 100644
index 5ee2a706..00000000
--- a/Ryujinx.HLE/HOS/Services/Friend/FriendErr.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Ryujinx.HLE.HOS.Services.Friend
-{
-    static class FriendErr
-    {
-        public const int InvalidArgument = 2;
-    }
-}
diff --git a/Ryujinx.HLE/HOS/Services/Friend/FriendError.cs b/Ryujinx.HLE/HOS/Services/Friend/FriendError.cs
new file mode 100644
index 00000000..49bc4c67
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Friend/FriendError.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Friend
+{
+    static class FriendError
+    {
+        public const int InvalidArgument        = 2;
+        public const int NotificationQueueEmpty = 15;
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Friend/FriendServicePermissionLevel.cs b/Ryujinx.HLE/HOS/Services/Friend/FriendServicePermissionLevel.cs
new file mode 100644
index 00000000..2ddb0b85
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Friend/FriendServicePermissionLevel.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Friend
+{
+    [Flags]
+    enum FriendServicePermissionLevel
+    {
+        UserMask    = 1,
+        OverlayMask = 2,
+        ManagerMask = 4,
+        SystemMask  = 8,
+
+        Admin   = -1,
+        User    = UserMask,
+        Overlay = UserMask | OverlayMask,
+        Manager = UserMask | OverlayMask | ManagerMask,
+        System  = UserMask | SystemMask
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs b/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs
index b1f23dd5..0de74d78 100644
--- a/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs
+++ b/Ryujinx.HLE/HOS/Services/Friend/IDaemonSuspendSessionService.cs
@@ -7,14 +7,22 @@ namespace Ryujinx.HLE.HOS.Services.Friend
     {
         private Dictionary<int, ServiceProcessRequest> _commands;
 
+        private FriendServicePermissionLevel PermissionLevel;
+
         public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
 
-        public IDaemonSuspendSessionService()
+        public IDaemonSuspendSessionService(FriendServicePermissionLevel permissionLevel)
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
-                // ...
+                //{ 0, Unknown0 }, // 4.0.0+
+                //{ 1, Unknown1 }, // 4.0.0+
+                //{ 2, Unknown2 }, // 4.0.0+
+                //{ 3, Unknown3 }, // 4.0.0+
+                //{ 4, Unknown4 }, // 4.0.0+
             };
+
+            PermissionLevel = permissionLevel;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs
index 35f40818..58e65ea7 100644
--- a/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs
+++ b/Ryujinx.HLE/HOS/Services/Friend/IFriendService.cs
@@ -1,8 +1,13 @@
+using Ryujinx.Common;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.SystemState;
 using Ryujinx.HLE.Utilities;
 using System.Collections.Generic;
+using System.IO;
+using System.Runtime.InteropServices;
+
+using static Ryujinx.HLE.HOS.ErrorCode;
 
 namespace Ryujinx.HLE.HOS.Services.Friend
 {
@@ -10,64 +15,174 @@ namespace Ryujinx.HLE.HOS.Services.Friend
     {
         private Dictionary<int, ServiceProcessRequest> _commands;
 
+        private FriendServicePermissionLevel _permissionLevel;
+
         public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
 
-        public IFriendService()
+        public IFriendService(FriendServicePermissionLevel permissionLevel)
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
-                { 10101, GetFriendList                 },
-                { 10600, DeclareOpenOnlinePlaySession  },
-                { 10601, DeclareCloseOnlinePlaySession },
-                { 10610, UpdateUserPresence            }
+              //{ 0,     GetCompletionEvent                                       },
+              //{ 1,     Cancel                                                   },
+                { 10100, GetFriendListIds                                         },
+                { 10101, GetFriendList                                            },
+              //{ 10102, UpdateFriendInfo                                         },
+              //{ 10110, GetFriendProfileImage                                    },
+              //{ 10200, SendFriendRequestForApplication                          },
+              //{ 10211, AddFacedFriendRequestForApplication                      },
+              //{ 10400, GetBlockedUserListIds                                    },
+              //{ 10500, GetProfileList                                           },
+                { 10600, DeclareOpenOnlinePlaySession                             },
+                { 10601, DeclareCloseOnlinePlaySession                            },
+                { 10610, UpdateUserPresence                                       },
+              //{ 10700, GetPlayHistoryRegistrationKey                            },
+              //{ 10701, GetPlayHistoryRegistrationKeyWithNetworkServiceAccountId },
+              //{ 10702, AddPlayHistory                                           },
+              //{ 11000, GetProfileImageUrl                                       },
+              //{ 20100, GetFriendCount                                           },
+              //{ 20101, GetNewlyFriendCount                                      },
+              //{ 20102, GetFriendDetailedInfo                                    },
+              //{ 20103, SyncFriendList                                           },
+              //{ 20104, RequestSyncFriendList                                    },
+              //{ 20110, LoadFriendSetting                                        },
+              //{ 20200, GetReceivedFriendRequestCount                            },
+              //{ 20201, GetFriendRequestList                                     },
+              //{ 20300, GetFriendCandidateList                                   },
+              //{ 20301, GetNintendoNetworkIdInfo                                 }, // 3.0.0+
+              //{ 20302, GetSnsAccountLinkage                                     }, // 5.0.0+
+              //{ 20303, GetSnsAccountProfile                                     }, // 5.0.0+
+              //{ 20304, GetSnsAccountFriendList                                  }, // 5.0.0+
+              //{ 20400, GetBlockedUserList                                       },
+              //{ 20401, SyncBlockedUserList                                      },
+              //{ 20500, GetProfileExtraList                                      },
+              //{ 20501, GetRelationship                                          },
+              //{ 20600, GetUserPresenceView                                      },
+              //{ 20700, GetPlayHistoryList                                       },
+              //{ 20701, GetPlayHistoryStatistics                                 },
+              //{ 20800, LoadUserSetting                                          },
+              //{ 20801, SyncUserSetting                                          },
+              //{ 20900, RequestListSummaryOverlayNotification                    },
+              //{ 21000, GetExternalApplicationCatalog                            },
+              //{ 30100, DropFriendNewlyFlags                                     },
+              //{ 30101, DeleteFriend                                             },
+              //{ 30110, DropFriendNewlyFlag                                      },
+              //{ 30120, ChangeFriendFavoriteFlag                                 },
+              //{ 30121, ChangeFriendOnlineNotificationFlag                       },
+              //{ 30200, SendFriendRequest                                        },
+              //{ 30201, SendFriendRequestWithApplicationInfo                     },
+              //{ 30202, CancelFriendRequest                                      },
+              //{ 30203, AcceptFriendRequest                                      },
+              //{ 30204, RejectFriendRequest                                      },
+              //{ 30205, ReadFriendRequest                                        },
+              //{ 30210, GetFacedFriendRequestRegistrationKey                     },
+              //{ 30211, AddFacedFriendRequest                                    },
+              //{ 30212, CancelFacedFriendRequest                                 },
+              //{ 30213, GetFacedFriendRequestProfileImage                        },
+              //{ 30214, GetFacedFriendRequestProfileImageFromPath                },
+              //{ 30215, SendFriendRequestWithExternalApplicationCatalogId        },
+              //{ 30216, ResendFacedFriendRequest                                 },
+              //{ 30217, SendFriendRequestWithNintendoNetworkIdInfo               }, // 3.0.0+
+              //{ 30300, GetSnsAccountLinkPageUrl                                 }, // 5.0.0+
+              //{ 30301, UnlinkSnsAccount                                         }, // 5.0.0+
+              //{ 30400, BlockUser                                                },
+              //{ 30401, BlockUserWithApplicationInfo                             },
+              //{ 30402, UnblockUser                                              },
+              //{ 30500, GetProfileExtraFromFriendCode                            },
+              //{ 30700, DeletePlayHistory                                        },
+              //{ 30810, ChangePresencePermission                                 },
+              //{ 30811, ChangeFriendRequestReception                             },
+              //{ 30812, ChangePlayLogPermission                                  },
+              //{ 30820, IssueFriendCode                                          },
+              //{ 30830, ClearPlayLog                                             },
+              //{ 49900, DeleteNetworkServiceAccountCache                         },
             };
+
+            _permissionLevel = permissionLevel;
         }
 
-        // nn::friends::GetFriendListGetFriendListIds(nn::account::Uid, int Unknown0, nn::friends::detail::ipc::SizedFriendFilter, ulong Unknown1) -> int CounterIds,  array<nn::account::NetworkServiceAccountId>
-        public long GetFriendList(ServiceCtx context)
+        // nn::friends::GetFriendListIds(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) -> int outCount, array<nn::account::NetworkServiceAccountId, 0xa>
+        public long GetFriendListIds(ServiceCtx context)
         {
-            UInt128 uuid = new UInt128(
-                context.RequestData.ReadInt64(),
-                context.RequestData.ReadInt64());
+            int offset = context.RequestData.ReadInt32();
 
-            int unknown0 = context.RequestData.ReadInt32();
+            // Padding
+            context.RequestData.ReadInt32();
 
-            FriendFilter filter = new FriendFilter
+            UInt128      uuid   = context.RequestData.ReadStruct<UInt128>();
+            FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
+
+            // Pid placeholder
+            context.RequestData.ReadInt64();
+
+            if (uuid.IsNull)
             {
-                PresenceStatus           = (PresenceStatusFilter)context.RequestData.ReadInt32(),
-                IsFavoriteOnly           = context.RequestData.ReadBoolean(),
-                IsSameAppPresenceOnly    = context.RequestData.ReadBoolean(),
-                IsSameAppPlayedOnly      = context.RequestData.ReadBoolean(),
-                IsArbitraryAppPlayedOnly = context.RequestData.ReadBoolean(),
-                PresenceGroupId          = context.RequestData.ReadInt64()
-            };
-
-            long unknown1 = context.RequestData.ReadInt64();
+                return MakeError(ErrorModule.Friends, FriendError.InvalidArgument);
+            }
 
             // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
             context.ResponseData.Write(0);
 
-            Logger.PrintStub(LogClass.ServiceFriend, new {
+            Logger.PrintStub(LogClass.ServiceFriend, new
+            {
                 UserId = uuid.ToString(),
-                unknown0,
+                offset,
                 filter.PresenceStatus,
                 filter.IsFavoriteOnly,
                 filter.IsSameAppPresenceOnly,
                 filter.IsSameAppPlayedOnly,
                 filter.IsArbitraryAppPlayedOnly,
                 filter.PresenceGroupId,
-                unknown1
             });
 
             return 0;
         }
 
-        // DeclareOpenOnlinePlaySession(nn::account::Uid)
+        // nn::friends::GetFriendList(int offset, nn::account::Uid userUUID, nn::friends::detail::ipc::SizedFriendFilter friendFilter, ulong pidPlaceHolder, pid) -> int outCount, array<nn::friends::detail::FriendImpl, 0x6>
+        public long GetFriendList(ServiceCtx context)
+        {
+            int offset = context.RequestData.ReadInt32();
+
+            // Padding
+            context.RequestData.ReadInt32();
+
+            UInt128      uuid   = context.RequestData.ReadStruct<UInt128>();
+            FriendFilter filter = context.RequestData.ReadStruct<FriendFilter>();
+
+            // Pid placeholder
+            context.RequestData.ReadInt64();
+
+            if (uuid.IsNull)
+            {
+                return MakeError(ErrorModule.Friends, FriendError.InvalidArgument);
+            }
+
+            // There are no friends online, so we return 0 because the nn::account::NetworkServiceAccountId array is empty.
+            context.ResponseData.Write(0);
+
+            Logger.PrintStub(LogClass.ServiceFriend, new {
+                UserId = uuid.ToString(),
+                offset,
+                filter.PresenceStatus,
+                filter.IsFavoriteOnly,
+                filter.IsSameAppPresenceOnly,
+                filter.IsSameAppPlayedOnly,
+                filter.IsArbitraryAppPlayedOnly,
+                filter.PresenceGroupId,
+            });
+
+            return 0;
+        }
+
+        // nn::friends::DeclareOpenOnlinePlaySession(nn::account::Uid)
         public long DeclareOpenOnlinePlaySession(ServiceCtx context)
         {
-            UInt128 uuid = new UInt128(
-                context.RequestData.ReadInt64(),
-                context.RequestData.ReadInt64());
+            UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
+
+            if (uuid.IsNull)
+            {
+                return MakeError(ErrorModule.Friends, FriendError.InvalidArgument);
+            }
 
             if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile))
             {
@@ -79,12 +194,15 @@ namespace Ryujinx.HLE.HOS.Services.Friend
             return 0;
         }
 
-        // DeclareCloseOnlinePlaySession(nn::account::Uid)
+        // nn::friends::DeclareCloseOnlinePlaySession(nn::account::Uid)
         public long DeclareCloseOnlinePlaySession(ServiceCtx context)
         {
-            UInt128 uuid = new UInt128(
-                context.RequestData.ReadInt64(),
-                context.RequestData.ReadInt64());
+            UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
+
+            if (uuid.IsNull)
+            {
+                return MakeError(ErrorModule.Friends, FriendError.InvalidArgument);
+            }
 
             if (context.Device.System.State.Account.TryGetUser(uuid, out UserProfile profile))
             {
@@ -96,21 +214,32 @@ namespace Ryujinx.HLE.HOS.Services.Friend
             return 0;
         }
 
-        // UpdateUserPresence(nn::account::Uid, ulong Unknown0) -> buffer<Unknown1, type: 0x19, size: 0xe0>
+        // nn::friends::UpdateUserPresence(nn::account::Uid, u64, pid, buffer<nn::friends::detail::UserPresenceImpl, 0x19>)
         public long UpdateUserPresence(ServiceCtx context)
         {
-            UInt128 uuid = new UInt128(
-                context.RequestData.ReadInt64(),
-                context.RequestData.ReadInt64());
+            UInt128 uuid = context.RequestData.ReadStruct<UInt128>();
 
-            long unknown0 = context.RequestData.ReadInt64();
+            // Pid placeholder
+            context.RequestData.ReadInt64();
 
             long position = context.Request.PtrBuff[0].Position;
             long size     = context.Request.PtrBuff[0].Size;
 
-            // TODO: Write the buffer content.
+            byte[] bufferContent = context.Memory.ReadBytes(position, size);
 
-            Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), unknown0 });
+            if (uuid.IsNull)
+            {
+                return MakeError(ErrorModule.Friends, FriendError.InvalidArgument);
+            }
+
+            int elementCount = bufferContent.Length / Marshal.SizeOf<UserPresence>();
+
+            using (BinaryReader bufferReader = new BinaryReader(new MemoryStream(bufferContent)))
+            {
+                UserPresence[] userPresenceInputArray = bufferReader.ReadStructArray<UserPresence>(elementCount);
+
+                Logger.PrintStub(LogClass.ServiceFriend, new { UserId = uuid.ToString(), userPresenceInputArray });
+            }
 
             return 0;
         }
diff --git a/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs b/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs
index 31459f7d..7eb1b43b 100644
--- a/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs
+++ b/Ryujinx.HLE/HOS/Services/Friend/IFriendServiceTypes.cs
@@ -1,6 +1,9 @@
+using Ryujinx.HLE.Utilities;
+using System.Runtime.InteropServices;
+
 namespace Ryujinx.HLE.HOS.Services.Friend
 {
-    enum PresenceStatusFilter
+    enum PresenceStatusFilter : uint
     {
         None,
         Online,
@@ -8,13 +11,94 @@ namespace Ryujinx.HLE.HOS.Services.Friend
         OnlineOrOnlinePlay
     }
 
+    enum PresenceStatus : uint
+    {
+        Offline,
+        Online,
+        OnlinePlay,
+    }
+
+    [StructLayout(LayoutKind.Sequential)]
     struct FriendFilter
     {
         public PresenceStatusFilter PresenceStatus;
-        public bool                 IsFavoriteOnly;
-        public bool                 IsSameAppPresenceOnly;
-        public bool                 IsSameAppPlayedOnly;
-        public bool                 IsArbitraryAppPlayedOnly;
-        public long                 PresenceGroupId;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsFavoriteOnly;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsSameAppPresenceOnly;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsSameAppPlayedOnly;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsArbitraryAppPlayedOnly;
+
+        public long PresenceGroupId;
+    }
+
+    [StructLayout(LayoutKind.Sequential, Pack = 0x8, CharSet = CharSet.Ansi)]
+    struct UserPresence
+    {
+        public UInt128        UserId;
+        public long           LastTimeOnlineTimestamp;
+        public PresenceStatus Status;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool SamePresenceGroupApplication;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x3)]
+        char[] Unknown;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0xC0)]
+        public char[] AppKeyValueStorage;
+
+        public override string ToString()
+        {
+            return $"UserPresence {{ UserId: {UserId}, LastTimeOnlineTimestamp: {LastTimeOnlineTimestamp}, Status: {Status}, AppKeyValueStorage: {AppKeyValueStorage} }}";
+        }
+    }
+
+    [StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x200, CharSet = CharSet.Ansi)]
+    struct Friend
+    {
+        public UInt128 UserId;
+        public long    NetworkUserId;
+
+        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x21)]
+        public string Nickname;
+
+        public UserPresence presence;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsFavourite;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsNew;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x6)]
+        char[] Unknown;
+
+        [MarshalAs(UnmanagedType.I1)]
+        public bool IsValid;
+    }
+
+    enum NotificationEventType : uint
+    {
+        Invalid          = 0x0,
+        FriendListUpdate = 0x1,
+        NewFriendRequest = 0x65,
+    }
+
+    [StructLayout(LayoutKind.Sequential, Pack = 0x8, Size = 0x10)]
+    struct NotificationInfo
+    {
+        public NotificationEventType Type;
+
+        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x4)]
+        char[] Padding;
+
+        public long NetworkUserIdPlaceholder;
     }
 }
diff --git a/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs b/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs
index 8b684e6b..68893efe 100644
--- a/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs
+++ b/Ryujinx.HLE/HOS/Services/Friend/INotificationService.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Threading;
@@ -5,37 +6,55 @@ using Ryujinx.HLE.Utilities;
 using System;
 using System.Collections.Generic;
 
+using static Ryujinx.HLE.HOS.ErrorCode;
+
 namespace Ryujinx.HLE.HOS.Services.Friend
 {
-    class INotificationService : IpcService
+    class INotificationService : IpcService, IDisposable
     {
-        private UInt128 _userId;
+        private readonly UInt128                      _userId;
+        private readonly FriendServicePermissionLevel _permissionLevel;
+
+        private readonly object _lock = new object();
 
         private KEvent _notificationEvent;
         private int    _notificationEventHandle = 0;
 
+
+        private LinkedList<NotificationInfo> _notifications;
+
+        private bool _hasNewFriendRequest;
+        private bool _hasFriendListUpdate;
+
         private Dictionary<int, ServiceProcessRequest> _commands;
 
         public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
 
-        public INotificationService(UInt128 userId)
+        public INotificationService(ServiceCtx context, UInt128 userId, FriendServicePermissionLevel permissionLevel)
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
                 { 0, GetEvent }, // 2.0.0+
-              //{ 1, Clear    }, // 2.0.0+
-              //{ 2, Pop      }, // 2.0.0+
+                { 1, Clear    }, // 2.0.0+
+                { 2, Pop      }, // 2.0.0+
             };
 
-            _userId = userId;
+            _userId            = userId;
+            _permissionLevel   = permissionLevel;
+            _notifications     = new LinkedList<NotificationInfo>();
+            _notificationEvent = new KEvent(context.Device.System);
+
+            _hasNewFriendRequest = false;
+            _hasFriendListUpdate = false;
+
+            NotificationEventHandler.Instance.RegisterNotificationService(this);
         }
 
+        // nn::friends::detail::ipc::INotificationService::GetEvent() -> handle<copy>
         public long GetEvent(ServiceCtx context)
         {
             if (_notificationEventHandle == 0)
             {
-                _notificationEvent = new KEvent(context.Device.System);
-
                 if (context.Process.HandleTable.GenerateHandle(_notificationEvent.ReadableEvent, out _notificationEventHandle) != KernelResult.Success)
                 {
                     throw new InvalidOperationException("Out of handles!");
@@ -46,5 +65,121 @@ namespace Ryujinx.HLE.HOS.Services.Friend
 
             return 0;
         }
+
+        // nn::friends::detail::ipc::INotificationService::Clear()
+        public long Clear(ServiceCtx context)
+        {
+            lock (_lock)
+            {
+                _hasNewFriendRequest = false;
+                _hasFriendListUpdate = false;
+
+                _notifications.Clear();
+            }
+
+            return 0;
+        }
+
+        // nn::friends::detail::ipc::INotificationService::Pop() -> nn::friends::detail::ipc::SizedNotificationInfo
+        public long Pop(ServiceCtx context)
+        {
+            lock (_lock)
+            {
+                if (_notifications.Count >= 1)
+                {
+                    NotificationInfo notificationInfo = _notifications.First.Value;
+                    _notifications.RemoveFirst();
+
+                    if (notificationInfo.Type == NotificationEventType.FriendListUpdate)
+                    {
+                        _hasFriendListUpdate = false;
+                    }
+                    else if (notificationInfo.Type == NotificationEventType.NewFriendRequest)
+                    {
+                        _hasNewFriendRequest = false;
+                    }
+
+                    context.ResponseData.WriteStruct(notificationInfo);
+
+                    return 0;
+                }
+            }
+
+            return MakeError(ErrorModule.Friends, FriendError.NotificationQueueEmpty);
+        }
+
+        public void SignalFriendListUpdate(UInt128 targetId)
+        {
+            lock (_lock)
+            {
+                if (_userId == targetId)
+                {
+                    if (!_hasFriendListUpdate)
+                    {
+                        NotificationInfo friendListNotification = new NotificationInfo();
+
+                        if (_notifications.Count != 0)
+                        {
+                            friendListNotification = _notifications.First.Value;
+                            _notifications.RemoveFirst();
+                        }
+
+                        friendListNotification.Type = NotificationEventType.FriendListUpdate;
+                        _hasFriendListUpdate = true;
+
+                        if (_hasNewFriendRequest)
+                        {
+                            NotificationInfo newFriendRequestNotification = new NotificationInfo();
+
+                            if (_notifications.Count != 0)
+                            {
+                                newFriendRequestNotification = _notifications.First.Value;
+                                _notifications.RemoveFirst();
+                            }
+
+                            newFriendRequestNotification.Type = NotificationEventType.NewFriendRequest;
+                            _notifications.AddFirst(newFriendRequestNotification);
+                        }
+
+                        // We defer this to make sure we are on top of the queue.
+                        _notifications.AddFirst(friendListNotification);
+                    }
+
+                    _notificationEvent.ReadableEvent.Signal();
+                }
+            }
+        }
+
+        public void SignalNewFriendRequest(UInt128 targetId)
+        {
+            lock (_lock)
+            {
+                if ((_permissionLevel & FriendServicePermissionLevel.OverlayMask) != 0 && _userId == targetId)
+                {
+                    if (!_hasNewFriendRequest)
+                    {
+                        if (_notifications.Count == 100)
+                        {
+                            SignalFriendListUpdate(targetId);
+                        }
+
+                        NotificationInfo newFriendRequestNotification = new NotificationInfo
+                        {
+                            Type = NotificationEventType.NewFriendRequest
+                        };
+
+                        _notifications.AddLast(newFriendRequestNotification);
+                        _hasNewFriendRequest = true;
+                    }
+
+                    _notificationEvent.ReadableEvent.Signal();
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            NotificationEventHandler.Instance.UnregisterNotificationService(this);
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
index 81281dc2..dea9d9ae 100644
--- a/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
+++ b/Ryujinx.HLE/HOS/Services/Friend/IServiceCreator.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common;
 using Ryujinx.HLE.HOS.Ipc;
 using Ryujinx.HLE.Utilities;
 using System.Collections.Generic;
@@ -10,9 +11,11 @@ namespace Ryujinx.HLE.HOS.Services.Friend
     {
         private Dictionary<int, ServiceProcessRequest> _commands;
 
+        private FriendServicePermissionLevel _permissionLevel;
+
         public override IReadOnlyDictionary<int, ServiceProcessRequest> Commands => _commands;
 
-        public IServiceCreator()
+        public IServiceCreator(FriendServicePermissionLevel permissionLevel)
         {
             _commands = new Dictionary<int, ServiceProcessRequest>
             {
@@ -20,35 +23,37 @@ namespace Ryujinx.HLE.HOS.Services.Friend
                 { 1, CreateNotificationService         }, // 2.0.0+
                 { 2, CreateDaemonSuspendSessionService }, // 4.0.0+
             };
+
+            _permissionLevel = permissionLevel;
         }
 
         // CreateFriendService() -> object<nn::friends::detail::ipc::IFriendService>
-        public static long CreateFriendService(ServiceCtx context)
+        public long CreateFriendService(ServiceCtx context)
         {
-            MakeObject(context, new IFriendService());
+            MakeObject(context, new IFriendService(_permissionLevel));
 
             return 0;
         }
 
         // CreateNotificationService(nn::account::Uid) -> object<nn::friends::detail::ipc::INotificationService>
-        public static long CreateNotificationService(ServiceCtx context)
+        public long CreateNotificationService(ServiceCtx context)
         {
-            UInt128 userId = new UInt128(context.RequestData.ReadBytes(0x10));
+            UInt128 userId = context.RequestData.ReadStruct<UInt128>();
 
             if (userId.IsNull)
             {
-                return MakeError(ErrorModule.Friends, FriendErr.InvalidArgument);
+                return MakeError(ErrorModule.Friends, FriendError.InvalidArgument);
             }
 
-            MakeObject(context, new INotificationService(userId));
+            MakeObject(context, new INotificationService(context, userId, _permissionLevel));
 
             return 0;
         }
 
         // CreateDaemonSuspendSessionService() -> object<nn::friends::detail::ipc::IDaemonSuspendSessionService>
-        public static long CreateDaemonSuspendSessionService(ServiceCtx context)
+        public long CreateDaemonSuspendSessionService(ServiceCtx context)
         {
-            MakeObject(context, new IDaemonSuspendSessionService());
+            MakeObject(context, new IDaemonSuspendSessionService(_permissionLevel));
 
             return 0;
         }
diff --git a/Ryujinx.HLE/HOS/Services/Friend/NotificationEventHandler.cs b/Ryujinx.HLE/HOS/Services/Friend/NotificationEventHandler.cs
new file mode 100644
index 00000000..8582a074
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Friend/NotificationEventHandler.cs
@@ -0,0 +1,83 @@
+using Ryujinx.HLE.Utilities;
+
+namespace Ryujinx.HLE.HOS.Services.Friend
+{
+    public sealed class NotificationEventHandler
+    {
+        private static NotificationEventHandler instance;
+        private static object                   instanceLock = new object();
+
+        private INotificationService[] _registry;
+
+        public static NotificationEventHandler Instance
+        {
+            get
+            {
+                lock (instanceLock)
+                {
+                    if (instance == null)
+                    {
+                        instance = new NotificationEventHandler();
+                    }
+
+                    return instance;
+                }
+            }
+        }
+
+        NotificationEventHandler()
+        {
+            _registry = new INotificationService[0x20];
+        }
+
+        internal void RegisterNotificationService(INotificationService service)
+        {
+            // NOTE: in case there isn't space anymore in the registry array, Nintendo doesn't return any errors.
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                if (_registry[i] == null)
+                {
+                    _registry[i] = service;
+                    break;
+                }
+            }
+        }
+
+        internal void UnregisterNotificationService(INotificationService service)
+        {
+            // NOTE: in case there isn't the entry in the registry array, Nintendo doesn't return any errors.
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                if (_registry[i] == service)
+                {
+                    _registry[i] = null;
+                    break;
+                }
+            }
+        }
+
+        // TODO: Use this when we will have enough things to go online.
+        public void SignalFriendListUpdate(UInt128 targetId)
+        {
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                if (_registry[i] != null)
+                {
+                    _registry[i].SignalFriendListUpdate(targetId);
+                }
+            }
+        }
+
+        // TODO: Use this when we will have enough things to go online.
+        public void SignalNewFriendRequest(UInt128 targetId)
+        {
+            for (int i = 0; i < _registry.Length; i++)
+            {
+                if (_registry[i] != null)
+                {
+                    _registry[i].SignalNewFriendRequest(targetId);
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
index 7207aaf0..a1afe9be 100644
--- a/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
+++ b/Ryujinx.HLE/HOS/Services/FspSrv/IFileSystemProxy.cs
@@ -1,6 +1,7 @@
 using LibHac;
 using LibHac.Fs;
 using LibHac.Fs.NcaUtils;
+using Ryujinx.Common;
 using Ryujinx.Common.Logging;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS.Ipc;
@@ -234,9 +235,7 @@ namespace Ryujinx.HLE.HOS.Services.FspSrv
 
             long titleId = context.RequestData.ReadInt64();
 
-            UInt128 userId = new UInt128(
-                context.RequestData.ReadInt64(), 
-                context.RequestData.ReadInt64());
+            UInt128 userId = context.RequestData.ReadStruct<UInt128>();
 
             long            saveId       = context.RequestData.ReadInt64();
             SaveDataType    saveDataType = (SaveDataType)context.RequestData.ReadByte();
diff --git a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
index 7cd943e0..3edb5619 100644
--- a/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
+++ b/Ryujinx.HLE/HOS/Services/ServiceFactory.cs
@@ -100,10 +100,19 @@ namespace Ryujinx.HLE.HOS.Services
                     return new IeTicketService();
 
                 case "friend:a":
-                    return new Friend.IServiceCreator();
+                    return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.Admin);
 
                 case "friend:u":
-                    return new Friend.IServiceCreator();
+                    return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.User);
+
+                case "friend:v":
+                    return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.Overlay);
+
+                case "friend:m":
+                    return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.Manager);
+
+                case "friend:s":
+                    return new Friend.IServiceCreator(Friend.FriendServicePermissionLevel.System);
 
                 case "fsp-srv":
                     return new IFileSystemProxy();
diff --git a/Ryujinx.HLE/Utilities/UInt128.cs b/Ryujinx.HLE/Utilities/UInt128.cs
index 8f5fc28f..22d87f6b 100644
--- a/Ryujinx.HLE/Utilities/UInt128.cs
+++ b/Ryujinx.HLE/Utilities/UInt128.cs
@@ -1,13 +1,15 @@
 using System;
 using System.IO;
 using System.Linq;
+using System.Runtime.InteropServices;
 
 namespace Ryujinx.HLE.Utilities
 {
-    public struct UInt128
+    [StructLayout(LayoutKind.Sequential)]
+    public struct UInt128 : IEquatable<UInt128>
     {
-        public long High { get; private set; }
-        public long Low  { get; private set; }
+        public readonly long Low;
+        public readonly long High;
 
         public bool IsNull => (Low | High) == 0;
 
@@ -45,9 +47,29 @@ namespace Ryujinx.HLE.Utilities
             return High.ToString("x16") + Low.ToString("x16");
         }
 
-        public bool IsZero()
+        public static bool operator ==(UInt128 x, UInt128 y)
         {
-            return (Low | High) == 0;
+            return x.Equals(y);
+        }
+
+        public static bool operator !=(UInt128 x, UInt128 y)
+        {
+            return !x.Equals(y);
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is UInt128 uint128 && Equals(uint128);
+        }
+
+        public bool Equals(UInt128 cmpObj)
+        {
+            return Low == cmpObj.Low && High == cmpObj.High;
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode.Combine(Low, High);
         }
     }
 }
\ No newline at end of file