From e19475481c2c2194f65d187df76662cee0421fdd Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Thu, 1 Mar 2018 01:08:47 +0200
Subject: [PATCH] Service/AM: convert to ServiceFramework

---
 src/core/hle/service/am/am.cpp     | 391 ++++++++---------
 src/core/hle/service/am/am.h       | 669 +++++++++++++++--------------
 src/core/hle/service/am/am_app.cpp |  36 +-
 src/core/hle/service/am/am_app.h   |  10 +-
 src/core/hle/service/am/am_net.cpp | 230 +++++-----
 src/core/hle/service/am/am_net.h   |  10 +-
 src/core/hle/service/am/am_sys.cpp | 126 +++---
 src/core/hle/service/am/am_sys.h   |  10 +-
 src/core/hle/service/am/am_u.cpp   | 150 ++++---
 src/core/hle/service/am/am_u.h     |  10 +-
 src/core/hle/service/service.cpp   |   3 +-
 11 files changed, 811 insertions(+), 834 deletions(-)

diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 7a65aaa6a..e7cca4ba5 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -3,7 +3,6 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
-#include <array>
 #include <cinttypes>
 #include <cstddef>
 #include <cstring>
@@ -27,7 +26,6 @@
 #include "core/hle/service/am/am_sys.h"
 #include "core/hle/service/am/am_u.h"
 #include "core/hle/service/fs/archive.h"
-#include "core/hle/service/service.h"
 #include "core/loader/loader.h"
 #include "core/loader/smdh.h"
 
@@ -41,11 +39,6 @@ constexpr u8 VARIATION_SYSTEM = 0x02;
 constexpr u32 TID_HIGH_UPDATE = 0x0004000E;
 constexpr u32 TID_HIGH_DLC = 0x0004008C;
 
-// CIA installation static context variables
-static bool cia_installing = false;
-
-static std::array<std::vector<u64_le>, 3> am_title_list;
-
 struct TitleInfo {
     u64_le tid;
     u64_le size;
@@ -453,7 +446,7 @@ std::string GetMediaTitlePath(Service::FS::MediaType media_type) {
     return "";
 }
 
-void ScanForTitles(Service::FS::MediaType media_type) {
+void Module::ScanForTitles(Service::FS::MediaType media_type) {
     am_title_list[static_cast<u32>(media_type)].clear();
 
     std::string title_path = GetMediaTitlePath(media_type);
@@ -472,31 +465,33 @@ void ScanForTitles(Service::FS::MediaType media_type) {
     }
 }
 
-void ScanForAllTitles() {
+void Module::ScanForAllTitles() {
     ScanForTitles(Service::FS::MediaType::NAND);
     ScanForTitles(Service::FS::MediaType::SDMC);
 }
 
-void GetNumPrograms(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1, 1, 0); // 0x00010040
+Module::Interface::Interface(std::shared_ptr<Module> am, const char* name, u32 max_session)
+    : ServiceFramework(name, max_session), am(std::move(am)) {}
+
+Module::Interface::~Interface() = default;
+
+void Module::Interface::GetNumPrograms(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1, 1, 0); // 0x00010040
     u32 media_type = rp.Pop<u8>();
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
-    rb.Push<u32>(am_title_list[media_type].size());
+    rb.Push<u32>(am->am_title_list[media_type].size());
 }
 
-void FindDLCContentInfos(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1002, 4, 4); // 0x10020104
+void Module::Interface::FindDLCContentInfos(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1002, 4, 4); // 0x10020104
 
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
     u64 title_id = rp.Pop<u64>();
     u32 content_count = rp.Pop<u32>();
-
-    size_t input_buffer_size, output_buffer_size;
-    IPC::MappedBufferPermissions input_buffer_perms, output_buffer_perms;
-    VAddr content_requested_in = rp.PopMappedBuffer(&input_buffer_size, &input_buffer_perms);
-    VAddr content_info_out = rp.PopMappedBuffer(&output_buffer_size, &output_buffer_perms);
+    auto& content_requested_in = rp.PopMappedBuffer();
+    auto& content_info_out = rp.PopMappedBuffer();
 
     // Validate that only DLC TIDs are passed in
     u32 tid_high = static_cast<u32>(title_id >> 32);
@@ -504,19 +499,20 @@ void FindDLCContentInfos(Service::Interface* self) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
         rb.Push(ResultCode(ErrCodes::InvalidTIDInList, ErrorModule::AM,
                            ErrorSummary::InvalidArgument, ErrorLevel::Usage));
-        rb.PushMappedBuffer(content_requested_in, input_buffer_size, input_buffer_perms);
-        rb.PushMappedBuffer(content_info_out, output_buffer_size, output_buffer_perms);
+        rb.PushMappedBuffer(content_requested_in);
+        rb.PushMappedBuffer(content_info_out);
         return;
     }
 
     std::vector<u16_le> content_requested(content_count);
-    Memory::ReadBlock(content_requested_in, content_requested.data(), content_count * sizeof(u16));
+    content_requested_in.Read(content_requested.data(), 0, content_count * sizeof(u16));
 
     std::string tmd_path = GetTitleMetadataPath(media_type, title_id);
 
     u32 content_read = 0;
     FileSys::TitleMetadata tmd;
     if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) {
+        std::size_t write_offset = 0;
         // Get info for each content index requested
         for (size_t i = 0; i < content_count; i++) {
             std::shared_ptr<FileUtil::IOFile> romfs_file;
@@ -533,29 +529,26 @@ void FindDLCContentInfos(Service::Interface* self) {
             content_info.size = tmd.GetContentSizeByIndex(content_requested[i]);
             content_info.romfs_size = romfs_size;
 
-            Memory::WriteBlock(content_info_out, &content_info, sizeof(ContentInfo));
-            content_info_out += sizeof(ContentInfo);
+            content_info_out.Write(&content_info, write_offset, sizeof(ContentInfo));
+            write_offset += sizeof(ContentInfo);
             content_read++;
         }
     }
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
     rb.Push(RESULT_SUCCESS);
-    rb.PushMappedBuffer(content_requested_in, input_buffer_size, input_buffer_perms);
-    rb.PushMappedBuffer(content_info_out, output_buffer_size, output_buffer_perms);
+    rb.PushMappedBuffer(content_requested_in);
+    rb.PushMappedBuffer(content_info_out);
 }
 
-void ListDLCContentInfos(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1003, 5, 2); // 0x10030142
+void Module::Interface::ListDLCContentInfos(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1003, 5, 2); // 0x10030142
 
     u32 content_count = rp.Pop<u32>();
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
     u64 title_id = rp.Pop<u64>();
     u32 start_index = rp.Pop<u32>();
-
-    size_t output_buffer_size;
-    IPC::MappedBufferPermissions output_buffer_perms;
-    VAddr content_info_out = rp.PopMappedBuffer(&output_buffer_size, &output_buffer_perms);
+    auto& content_info_out = rp.PopMappedBuffer();
 
     // Validate that only DLC TIDs are passed in
     u32 tid_high = static_cast<u32>(title_id >> 32);
@@ -564,7 +557,7 @@ void ListDLCContentInfos(Service::Interface* self) {
         rb.Push(ResultCode(ErrCodes::InvalidTIDInList, ErrorModule::AM,
                            ErrorSummary::InvalidArgument, ErrorLevel::Usage));
         rb.Push<u32>(0);
-        rb.PushMappedBuffer(content_info_out, output_buffer_size, output_buffer_perms);
+        rb.PushMappedBuffer(content_info_out);
         return;
     }
 
@@ -574,6 +567,7 @@ void ListDLCContentInfos(Service::Interface* self) {
     FileSys::TitleMetadata tmd;
     if (tmd.Load(tmd_path) == Loader::ResultStatus::Success) {
         copied = std::min(content_count, static_cast<u32>(tmd.GetContentCount()));
+        std::size_t write_offset = 0;
         for (u32 i = start_index; i < copied; i++) {
             std::shared_ptr<FileUtil::IOFile> romfs_file;
             u64 romfs_offset = 0;
@@ -589,58 +583,61 @@ void ListDLCContentInfos(Service::Interface* self) {
             content_info.size = tmd.GetContentSizeByIndex(i);
             content_info.romfs_size = romfs_size;
 
-            Memory::WriteBlock(content_info_out, &content_info, sizeof(ContentInfo));
-            content_info_out += sizeof(ContentInfo);
+            content_info_out.Write(&content_info, write_offset, sizeof(ContentInfo));
+            write_offset += sizeof(ContentInfo);
         }
     }
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
     rb.Push(RESULT_SUCCESS);
     rb.Push(copied);
-    rb.PushMappedBuffer(content_info_out, output_buffer_size, output_buffer_perms);
+    rb.PushMappedBuffer(content_info_out);
 }
 
-void DeleteContents(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1004, 4, 2); // 0x10040102
+void Module::Interface::DeleteContents(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1004, 4, 2); // 0x10040102
     u8 media_type = rp.Pop<u8>();
     u64 title_id = rp.Pop<u64>();
     u32 content_count = rp.Pop<u32>();
-    VAddr content_ids_in = rp.PopMappedBuffer(nullptr);
+    auto& content_ids_in = rp.PopMappedBuffer();
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     rb.Push(RESULT_SUCCESS);
-    LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, title_id=0x%016" PRIx64
-                            ", content_count=%u, content_ids_in=0x%08x",
-                media_type, title_id, content_count, content_ids_in);
+    rb.PushMappedBuffer(content_ids_in);
+    LOG_WARNING(Service_AM, "(STUBBED) media_type=%u, title_id=0x%016" PRIx64 ", content_count=%u",
+                media_type, title_id, content_count);
 }
 
-void GetProgramList(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 2, 2, 2); // 0x00020082
+void Module::Interface::GetProgramList(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 2, 2, 2); // 0x00020082
 
     u32 count = rp.Pop<u32>();
     u8 media_type = rp.Pop<u8>();
-    VAddr title_ids_output_pointer = rp.PopMappedBuffer(nullptr);
+    auto& title_ids_output_pointer = rp.PopMappedBuffer();
 
-    if (!Memory::IsValidVirtualAddress(title_ids_output_pointer) || media_type > 2) {
-        IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    if (media_type > 2) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
         rb.Push<u32>(-1); // TODO(shinyquagsire23): Find the right error code
         rb.Push<u32>(0);
+        rb.PushMappedBuffer(title_ids_output_pointer);
         return;
     }
 
-    u32 media_count = static_cast<u32>(am_title_list[media_type].size());
+    u32 media_count = static_cast<u32>(am->am_title_list[media_type].size());
     u32 copied = std::min(media_count, count);
 
-    Memory::WriteBlock(title_ids_output_pointer, am_title_list[media_type].data(),
-                       copied * sizeof(u64));
+    title_ids_output_pointer.Write(am->am_title_list[media_type].data(), 0, copied * sizeof(u64));
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
     rb.Push(RESULT_SUCCESS);
     rb.Push(copied);
+    rb.PushMappedBuffer(title_ids_output_pointer);
 }
 
 ResultCode GetTitleInfoFromList(const std::vector<u64>& title_id_list,
-                                Service::FS::MediaType media_type, VAddr title_info_out) {
+                                Service::FS::MediaType media_type,
+                                Kernel::MappedBuffer& title_info_out) {
+    std::size_t write_offset = 0;
     for (u32 i = 0; i < title_id_list.size(); i++) {
         std::string tmd_path = GetTitleMetadataPath(media_type, title_id_list[i]);
 
@@ -658,37 +655,34 @@ ResultCode GetTitleInfoFromList(const std::vector<u64>& title_id_list,
             return ResultCode(ErrorDescription::NotFound, ErrorModule::AM,
                               ErrorSummary::InvalidState, ErrorLevel::Permanent);
         }
-        Memory::WriteBlock(title_info_out, &title_info, sizeof(TitleInfo));
-        title_info_out += sizeof(TitleInfo);
+        title_info_out.Write(&title_info, write_offset, sizeof(TitleInfo));
+        write_offset += sizeof(TitleInfo);
     }
 
     return RESULT_SUCCESS;
 }
 
-void GetProgramInfos(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 3, 2, 4); // 0x00030084
+void Module::Interface::GetProgramInfos(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 3, 2, 4); // 0x00030084
 
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
     u32 title_count = rp.Pop<u32>();
-
-    size_t title_id_list_size, title_info_size;
-    IPC::MappedBufferPermissions title_id_list_perms, title_info_perms;
-    VAddr title_id_list_pointer = rp.PopMappedBuffer(&title_id_list_size, &title_id_list_perms);
-    VAddr title_info_out = rp.PopMappedBuffer(&title_info_size, &title_info_perms);
+    auto& title_id_list_buffer = rp.PopMappedBuffer();
+    auto& title_info_out = rp.PopMappedBuffer();
 
     std::vector<u64> title_id_list(title_count);
-    Memory::ReadBlock(title_id_list_pointer, title_id_list.data(), title_count * sizeof(u64));
+    title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64));
 
     ResultCode result = GetTitleInfoFromList(title_id_list, media_type, title_info_out);
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
     rb.Push(result);
-    rb.PushMappedBuffer(title_id_list_pointer, title_id_list_size, title_id_list_perms);
-    rb.PushMappedBuffer(title_info_out, title_info_size, title_info_perms);
+    rb.PushMappedBuffer(title_id_list_buffer);
+    rb.PushMappedBuffer(title_info_out);
 }
 
-void DeleteUserProgram(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x000400, 3, 0);
+void Module::Interface::DeleteUserProgram(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x000400, 3, 0);
     auto media_type = rp.PopEnum<FS::MediaType>();
     u32 low = rp.Pop<u32>();
     u32 high = rp.Pop<u32>();
@@ -711,25 +705,22 @@ void DeleteUserProgram(Service::Interface* self) {
         return;
     }
     bool success = FileUtil::DeleteDirRecursively(path);
-    ScanForAllTitles();
+    am->ScanForAllTitles();
     rb.Push(RESULT_SUCCESS);
     if (!success)
         LOG_ERROR(Service_AM, "FileUtil::DeleteDirRecursively unexpectedly failed");
 }
 
-void GetDLCTitleInfos(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1005, 2, 4); // 0x10050084
+void Module::Interface::GetDLCTitleInfos(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1005, 2, 4); // 0x10050084
 
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
     u32 title_count = rp.Pop<u32>();
-
-    size_t title_id_list_size, title_info_size;
-    IPC::MappedBufferPermissions title_id_list_perms, title_info_perms;
-    VAddr title_id_list_pointer = rp.PopMappedBuffer(&title_id_list_size, &title_id_list_perms);
-    VAddr title_info_out = rp.PopMappedBuffer(&title_info_size, &title_info_perms);
+    auto& title_id_list_buffer = rp.PopMappedBuffer();
+    auto& title_info_out = rp.PopMappedBuffer();
 
     std::vector<u64> title_id_list(title_count);
-    Memory::ReadBlock(title_id_list_pointer, title_id_list.data(), title_count * sizeof(u64));
+    title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64));
 
     ResultCode result = RESULT_SUCCESS;
 
@@ -749,23 +740,20 @@ void GetDLCTitleInfos(Service::Interface* self) {
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
     rb.Push(result);
-    rb.PushMappedBuffer(title_id_list_pointer, title_id_list_size, title_id_list_perms);
-    rb.PushMappedBuffer(title_info_out, title_info_size, title_info_perms);
+    rb.PushMappedBuffer(title_id_list_buffer);
+    rb.PushMappedBuffer(title_info_out);
 }
 
-void GetPatchTitleInfos(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x100D, 2, 4); // 0x100D0084
+void Module::Interface::GetPatchTitleInfos(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x100D, 2, 4); // 0x100D0084
 
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
     u32 title_count = rp.Pop<u32>();
-
-    size_t title_id_list_size, title_info_size;
-    IPC::MappedBufferPermissions title_id_list_perms, title_info_perms;
-    VAddr title_id_list_pointer = rp.PopMappedBuffer(&title_id_list_size, &title_id_list_perms);
-    VAddr title_info_out = rp.PopMappedBuffer(&title_info_size, &title_info_perms);
+    auto& title_id_list_buffer = rp.PopMappedBuffer();
+    auto& title_info_out = rp.PopMappedBuffer();
 
     std::vector<u64> title_id_list(title_count);
-    Memory::ReadBlock(title_id_list_pointer, title_id_list.data(), title_count * sizeof(u64));
+    title_id_list_buffer.Read(title_id_list.data(), 0, title_count * sizeof(u64));
 
     ResultCode result = RESULT_SUCCESS;
 
@@ -785,39 +773,40 @@ void GetPatchTitleInfos(Service::Interface* self) {
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 4);
     rb.Push(result);
-    rb.PushMappedBuffer(title_id_list_pointer, title_id_list_size, title_id_list_perms);
-    rb.PushMappedBuffer(title_info_out, title_info_size, title_info_perms);
+    rb.PushMappedBuffer(title_id_list_buffer);
+    rb.PushMappedBuffer(title_info_out);
 }
 
-void ListDataTitleTicketInfos(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1007, 4, 4); // 0x10070102
+void Module::Interface::ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1007, 4, 2); // 0x10070102
     u32 ticket_count = rp.Pop<u32>();
     u64 title_id = rp.Pop<u64>();
     u32 start_index = rp.Pop<u32>();
-    VAddr ticket_info_out = rp.PopMappedBuffer(nullptr);
-    VAddr ticket_info_write = ticket_info_out;
+    auto& ticket_info_out = rp.PopMappedBuffer();
 
+    std::size_t write_offset = 0;
     for (u32 i = 0; i < ticket_count; i++) {
         TicketInfo ticket_info = {};
         ticket_info.title_id = title_id;
         ticket_info.version = 0; // TODO
         ticket_info.size = 0;    // TODO
 
-        Memory::WriteBlock(ticket_info_write, &ticket_info, sizeof(TicketInfo));
-        ticket_info_write += sizeof(TicketInfo);
+        ticket_info_out.Write(&ticket_info, write_offset, sizeof(TicketInfo));
+        write_offset += sizeof(TicketInfo);
     }
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
     rb.Push(RESULT_SUCCESS);
     rb.Push(ticket_count);
+    rb.PushMappedBuffer(ticket_info_out);
 
-    LOG_WARNING(Service_AM, "(STUBBED) ticket_count=0x%08X, title_id=0x%016" PRIx64
-                            ", start_index=0x%08X, ticket_info_out=0x%08X",
-                ticket_count, title_id, start_index, ticket_info_out);
+    LOG_WARNING(Service_AM,
+                "(STUBBED) ticket_count=0x%08X, title_id=0x%016" PRIx64 ", start_index=0x%08X",
+                ticket_count, title_id, start_index);
 }
 
-void GetDLCContentInfoCount(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1001, 3, 0); // 0x100100C0
+void Module::Interface::GetDLCContentInfoCount(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1001, 3, 0); // 0x100100C0
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
     u64 title_id = rp.Pop<u64>();
 
@@ -846,8 +835,8 @@ void GetDLCContentInfoCount(Service::Interface* self) {
     }
 }
 
-void DeleteTicket(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 7, 2, 0); // 0x00070080
+void Module::Interface::DeleteTicket(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 7, 2, 0); // 0x00070080
     u64 title_id = rp.Pop<u64>();
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@@ -855,8 +844,8 @@ void DeleteTicket(Service::Interface* self) {
     LOG_WARNING(Service_AM, "(STUBBED) called title_id=0x%016" PRIx64 "", title_id);
 }
 
-void GetNumTickets(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 8, 0, 0); // 0x00080000
+void Module::Interface::GetNumTickets(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 8, 0, 0); // 0x00080000
     u32 ticket_count = 0;
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
@@ -865,22 +854,22 @@ void GetNumTickets(Service::Interface* self) {
     LOG_WARNING(Service_AM, "(STUBBED) called ticket_count=0x%08x", ticket_count);
 }
 
-void GetTicketList(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 9, 2, 2); // 0x00090082
+void Module::Interface::GetTicketList(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 9, 2, 2); // 0x00090082
     u32 ticket_list_count = rp.Pop<u32>();
     u32 ticket_index = rp.Pop<u32>();
-    VAddr ticket_tids_out = rp.PopMappedBuffer(nullptr);
+    auto& ticket_tids_out = rp.PopMappedBuffer();
 
-    IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+    IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
     rb.Push(RESULT_SUCCESS);
     rb.Push(ticket_list_count);
-    LOG_WARNING(Service_AM,
-                "(STUBBED) ticket_list_count=0x%08x, ticket_index=0x%08x, ticket_tids_out=0x%08x",
-                ticket_list_count, ticket_index, ticket_tids_out);
+    rb.PushMappedBuffer(ticket_tids_out);
+    LOG_WARNING(Service_AM, "(STUBBED) ticket_list_count=0x%08x, ticket_index=0x%08x",
+                ticket_list_count, ticket_index);
 }
 
-void QueryAvailableTitleDatabase(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x19, 1, 0); // 0x190040
+void Module::Interface::QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x19, 1, 0); // 0x190040
     u8 media_type = rp.Pop<u8>();
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
@@ -890,8 +879,8 @@ void QueryAvailableTitleDatabase(Service::Interface* self) {
     LOG_WARNING(Service_AM, "(STUBBED) media_type=%u", media_type);
 }
 
-void CheckContentRights(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x25, 3, 0); // 0x2500C0
+void Module::Interface::CheckContentRights(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x25, 3, 0); // 0x2500C0
     u64 tid = rp.Pop<u64>();
     u16 content_index = rp.Pop<u16>();
 
@@ -906,8 +895,8 @@ void CheckContentRights(Service::Interface* self) {
     LOG_WARNING(Service_AM, "(STUBBED) tid=%016" PRIx64 ", content_index=%u", tid, content_index);
 }
 
-void CheckContentRightsIgnorePlatform(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2D, 3, 0); // 0x2D00C0
+void Module::Interface::CheckContentRightsIgnorePlatform(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x2D, 3, 0); // 0x2D00C0
     u64 tid = rp.Pop<u64>();
     u16 content_index = rp.Pop<u16>();
 
@@ -922,11 +911,11 @@ void CheckContentRightsIgnorePlatform(Service::Interface* self) {
     LOG_WARNING(Service_AM, "(STUBBED) tid=%016" PRIx64 ", content_index=%u", tid, content_index);
 }
 
-void BeginImportProgram(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0402, 1, 0); // 0x04020040
+void Module::Interface::BeginImportProgram(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0402, 1, 0); // 0x04020040
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
 
-    if (cia_installing) {
+    if (am->cia_installing) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(ResultCode(ErrCodes::CIACurrentlyInstalling, ErrorModule::AM,
                            ErrorSummary::InvalidState, ErrorLevel::Permanent));
@@ -939,33 +928,32 @@ void BeginImportProgram(Service::Interface* self) {
     auto file =
         std::make_shared<Service::FS::File>(std::make_unique<CIAFile>(media_type), cia_path);
 
-    cia_installing = true;
+    am->cia_installing = true;
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     rb.Push(RESULT_SUCCESS); // No error
-    rb.PushCopyHandles(Kernel::g_handle_table.Create(file->Connect()).Unwrap());
+    rb.PushCopyObjects(file->Connect());
 
     LOG_WARNING(Service_AM, "(STUBBED) media_type=%u", static_cast<u32>(media_type));
 }
 
-void EndImportProgram(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0405, 0, 2); // 0x04050002
-    auto cia_handle = rp.PopHandle();
+void Module::Interface::EndImportProgram(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0405, 0, 2); // 0x04050002
+    auto cia = rp.PopObject<Kernel::ClientSession>();
 
-    Kernel::g_handle_table.Close(cia_handle);
-    ScanForAllTitles();
+    am->ScanForAllTitles();
 
-    cia_installing = false;
+    am->cia_installing = false;
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     rb.Push(RESULT_SUCCESS);
 }
 
-ResultVal<std::shared_ptr<Service::FS::File>> GetFileFromHandle(Kernel::Handle handle) {
-    // Step up the chain from Handle->ClientSession->ServerSession and then
+ResultVal<std::shared_ptr<Service::FS::File>> GetFileFromSession(
+    Kernel::SharedPtr<Kernel::ClientSession> file_session) {
+    // Step up the chain from ClientSession->ServerSession and then
     // cast to File. For AM on 3DS, invalid handles actually hang the system.
-    auto file_session = Kernel::g_handle_table.Get<Kernel::ClientSession>(handle);
 
-    if (file_session == nullptr || file_session->parent == nullptr) {
+    if (file_session->parent == nullptr) {
         LOG_WARNING(Service_AM, "Invalid file handle!");
         return Kernel::ERR_INVALID_HANDLE;
     }
@@ -993,12 +981,12 @@ ResultVal<std::shared_ptr<Service::FS::File>> GetFileFromHandle(Kernel::Handle h
     return Kernel::ERR_NOT_IMPLEMENTED;
 }
 
-void GetProgramInfoFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0408, 1, 2); // 0x04080042
+void Module::Interface::GetProgramInfoFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0408, 1, 2); // 0x04080042
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
+    auto cia = rp.PopObject<Kernel::ClientSession>();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(file_res.Code());
@@ -1031,65 +1019,61 @@ void GetProgramInfoFromCia(Service::Interface* self) {
     rb.PushRaw<TitleInfo>(title_info);
 }
 
-void GetSystemMenuDataFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0409, 0, 4); // 0x04090004
+void Module::Interface::GetSystemMenuDataFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0409, 0, 4); // 0x04090004
+    auto cia = rp.PopObject<Kernel::ClientSession>();
+    auto& output_buffer = rp.PopMappedBuffer();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
-        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
         rb.Push(file_res.Code());
+        rb.PushMappedBuffer(output_buffer);
         return;
     }
 
-    size_t output_buffer_size;
-    IPC::MappedBufferPermissions output_buffer_perms;
-    VAddr output_buffer = rp.PopMappedBuffer(&output_buffer_size, &output_buffer_perms);
-    output_buffer_size = std::min(output_buffer_size, sizeof(Loader::SMDH));
+    std::size_t output_buffer_size = std::min(output_buffer.GetSize(), sizeof(Loader::SMDH));
 
     auto file = file_res.Unwrap();
     FileSys::CIAContainer container;
     if (container.Load(*file->backend) != Loader::ResultStatus::Success) {
-        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM,
                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent));
+        rb.PushMappedBuffer(output_buffer);
         return;
     }
     std::vector<u8> temp(output_buffer_size);
 
     //  Read from the Meta offset + 0x400 for the 0x36C0-large SMDH
-    auto read_result =
-        file->backend->Read(container.GetMetadataOffset() + FileSys::CIA_METADATA_SIZE,
-                            output_buffer_size, temp.data());
-    if (read_result.Failed() || *read_result != output_buffer_size) {
-        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+    auto read_result = file->backend->Read(
+        container.GetMetadataOffset() + FileSys::CIA_METADATA_SIZE, temp.size(), temp.data());
+    if (read_result.Failed() || *read_result != temp.size()) {
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM,
                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent));
+        rb.PushMappedBuffer(output_buffer);
         return;
     }
 
-    Memory::WriteBlock(output_buffer, temp.data(), output_buffer_size);
+    output_buffer.Write(temp.data(), 0, temp.size());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
-    rb.PushMappedBuffer(output_buffer, output_buffer_size, output_buffer_perms);
     rb.Push(RESULT_SUCCESS);
+    rb.PushMappedBuffer(output_buffer);
 }
 
-void GetDependencyListFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040A, 0, 2); // 0x040A0002
+void Module::Interface::GetDependencyListFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x040A, 0, 2); // 0x040A0002
+    auto cia = rp.PopObject<Kernel::ClientSession>();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(file_res.Code());
         return;
     }
 
-    size_t output_buffer_size;
-    VAddr output_buffer = rp.PeekStaticBuffer(0, &output_buffer_size);
-    output_buffer_size = std::min(output_buffer_size, FileSys::CIA_DEPENDENCY_SIZE);
-
     auto file = file_res.Unwrap();
     FileSys::CIAContainer container;
     if (container.Load(*file->backend) != Loader::ResultStatus::Success) {
@@ -1099,18 +1083,19 @@ void GetDependencyListFromCia(Service::Interface* self) {
         return;
     }
 
-    Memory::WriteBlock(output_buffer, container.GetDependencies().data(), output_buffer_size);
+    std::vector<u8> buffer(FileSys::CIA_DEPENDENCY_SIZE);
+    std::memcpy(buffer.data(), container.GetDependencies().data(), buffer.size());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     rb.Push(RESULT_SUCCESS);
-    rb.PushStaticBuffer(output_buffer, output_buffer_size, 0);
+    rb.PushStaticBuffer(buffer, 0);
 }
 
-void GetTransferSizeFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040B, 0, 2); // 0x040B0002
+void Module::Interface::GetTransferSizeFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x040B, 0, 2); // 0x040B0002
+    auto cia = rp.PopObject<Kernel::ClientSession>();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(file_res.Code());
@@ -1131,11 +1116,11 @@ void GetTransferSizeFromCia(Service::Interface* self) {
     rb.Push(container.GetMetadataOffset());
 }
 
-void GetCoreVersionFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040C, 0, 2); // 0x040C0002
+void Module::Interface::GetCoreVersionFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x040C, 0, 2); // 0x040C0002
+    auto cia = rp.PopObject<Kernel::ClientSession>();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(file_res.Code());
@@ -1156,12 +1141,12 @@ void GetCoreVersionFromCia(Service::Interface* self) {
     rb.Push(container.GetCoreVersion());
 }
 
-void GetRequiredSizeFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x040D, 1, 2); // 0x040D0042
+void Module::Interface::GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x040D, 1, 2); // 0x040D0042
     auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
+    auto cia = rp.PopObject<Kernel::ClientSession>();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(file_res.Code());
@@ -1185,8 +1170,8 @@ void GetRequiredSizeFromCia(Service::Interface* self) {
     rb.Push(container.GetTitleMetadata().GetContentSizeByIndex(FileSys::TMDContentIndex::Main));
 }
 
-void DeleteProgram(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0410, 3, 0);
+void Module::Interface::DeleteProgram(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0410, 3, 0);
     auto media_type = rp.PopEnum<FS::MediaType>();
     u32 low = rp.Pop<u32>();
     u32 high = rp.Pop<u32>();
@@ -1201,17 +1186,17 @@ void DeleteProgram(Service::Interface* self) {
         return;
     }
     bool success = FileUtil::DeleteDirRecursively(path);
-    ScanForAllTitles();
+    am->ScanForAllTitles();
     rb.Push(RESULT_SUCCESS);
     if (!success)
         LOG_ERROR(Service_AM, "FileUtil::DeleteDirRecursively unexpectedly failed");
 }
 
-void GetMetaSizeFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0413, 0, 2); // 0x04130002
+void Module::Interface::GetMetaSizeFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0413, 0, 2); // 0x04130002
+    auto cia = rp.PopObject<Kernel::ClientSession>();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(file_res.Code());
@@ -1233,31 +1218,30 @@ void GetMetaSizeFromCia(Service::Interface* self) {
     rb.Push(container.GetMetadataSize());
 }
 
-void GetMetaDataFromCia(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0414, 0, 2); // 0x04140044
+void Module::Interface::GetMetaDataFromCia(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0414, 1, 4); // 0x04140044
 
     u32 output_size = rp.Pop<u32>();
+    auto cia = rp.PopObject<Kernel::ClientSession>();
+    auto& output_buffer = rp.PopMappedBuffer();
 
-    // Get a File from our Handle
-    auto file_res = GetFileFromHandle(rp.PopHandle());
+    auto file_res = GetFileFromSession(cia);
     if (!file_res.Succeeded()) {
-        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
         rb.Push(file_res.Code());
+        rb.PushMappedBuffer(output_buffer);
         return;
     }
-
-    size_t output_buffer_size;
-    VAddr output_buffer = rp.PeekStaticBuffer(0, &output_buffer_size);
-
     // Don't write beyond the actual static buffer size.
-    output_size = std::min(static_cast<u32>(output_buffer_size), output_size);
+    output_size = std::min(static_cast<u32>(output_buffer.GetSize()), output_size);
 
     auto file = file_res.Unwrap();
     FileSys::CIAContainer container;
     if (container.Load(*file->backend) != Loader::ResultStatus::Success) {
-        IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+        IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
         rb.Push(ResultCode(ErrCodes::InvalidCIAHeader, ErrorModule::AM,
                            ErrorSummary::InvalidArgument, ErrorLevel::Permanent));
+        rb.PushMappedBuffer(output_buffer);
         return;
     }
 
@@ -1271,23 +1255,24 @@ void GetMetaDataFromCia(Service::Interface* self) {
         return;
     }
 
-    Memory::WriteBlock(output_buffer, temp.data(), output_size);
+    output_buffer.Write(temp.data(), 0, output_size);
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     rb.Push(RESULT_SUCCESS);
-    rb.PushStaticBuffer(output_buffer, output_buffer_size, 0);
+    rb.PushMappedBuffer(output_buffer);
 }
 
-void Init() {
-    AddService(new AM_APP_Interface);
-    AddService(new AM_NET_Interface);
-    AddService(new AM_SYS_Interface);
-    AddService(new AM_U_Interface);
-
+Module::Module() {
     ScanForAllTitles();
 }
 
-void Shutdown() {
-    cia_installing = false;
+Module::~Module() = default;
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+    auto am = std::make_shared<Module>();
+    std::make_shared<AM_APP>(am)->InstallAsService(service_manager);
+    std::make_shared<AM_NET>(am)->InstallAsService(service_manager);
+    std::make_shared<AM_SYS>(am)->InstallAsService(service_manager);
+    std::make_shared<AM_U>(am)->InstallAsService(service_manager);
 }
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index dfed1b523..3492d1405 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -4,12 +4,15 @@
 
 #pragma once
 
+#include <array>
 #include <functional>
 #include <string>
+#include <vector>
 #include "common/common_types.h"
 #include "core/file_sys/cia_container.h"
 #include "core/file_sys/file_backend.h"
 #include "core/hle/result.h"
+#include "core/hle/service/service.h"
 
 namespace Service {
 namespace FS {
@@ -18,9 +21,6 @@ enum class MediaType : u32;
 }
 
 namespace Service {
-
-class Interface;
-
 namespace AM {
 
 namespace ErrCodes {
@@ -138,360 +138,377 @@ std::string GetTitlePath(Service::FS::MediaType media_type, u64 tid);
  */
 std::string GetMediaTitlePath(Service::FS::MediaType media_type);
 
-/**
- * Scans the for titles in a storage medium for listing.
- * @param media_type the storage medium to scan
- */
-void ScanForTitles(Service::FS::MediaType media_type);
+class Module final {
+public:
+    Module();
+    ~Module();
 
-/**
- * Scans all storage mediums for titles for listing.
- */
-void ScanForAllTitles();
+    class Interface : public ServiceFramework<Interface> {
+    public:
+        Interface(std::shared_ptr<Module> am, const char* name, u32 max_session);
+        ~Interface();
 
-/**
- * AM::GetNumPrograms service function
- * Gets the number of installed titles in the requested media type
- *  Inputs:
- *      0 : Command header (0x00010040)
- *      1 : Media type to load the titles from
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : The number of titles in the requested media type
- */
-void GetNumPrograms(Service::Interface* self);
+    protected:
+        /**
+         * AM::GetNumPrograms service function
+         * Gets the number of installed titles in the requested media type
+         *  Inputs:
+         *      0 : Command header (0x00010040)
+         *      1 : Media type to load the titles from
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : The number of titles in the requested media type
+         */
+        void GetNumPrograms(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::FindDLCContentInfos service function
- * Explicitly checks that TID high value is 0004008C or an error is returned.
- *  Inputs:
- *      1 : MediaType
- *    2-3 : u64, Title ID
- *      4 : Content count
- *      6 : Content IDs pointer
- *      8 : Content Infos pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void FindDLCContentInfos(Service::Interface* self);
+        /**
+         * AM::FindDLCContentInfos service function
+         * Explicitly checks that TID high value is 0004008C or an error is returned.
+         *  Inputs:
+         *      1 : MediaType
+         *    2-3 : u64, Title ID
+         *      4 : Content count
+         *      6 : Content IDs pointer
+         *      8 : Content Infos pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void FindDLCContentInfos(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::ListDLCContentInfos service function
- * Explicitly checks that TID high value is 0004008C or an error is returned.
- *  Inputs:
- *      1 : Content count
- *      2 : MediaType
- *    3-4 : u64, Title ID
- *      5 : Start Index
- *      7 : Content Infos pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Number of content infos returned
- */
-void ListDLCContentInfos(Service::Interface* self);
+        /**
+         * AM::ListDLCContentInfos service function
+         * Explicitly checks that TID high value is 0004008C or an error is returned.
+         *  Inputs:
+         *      1 : Content count
+         *      2 : MediaType
+         *    3-4 : u64, Title ID
+         *      5 : Start Index
+         *      7 : Content Infos pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Number of content infos returned
+         */
+        void ListDLCContentInfos(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::DeleteContents service function
- *  Inputs:
- *      1 : MediaType
- *    2-3 : u64, Title ID
- *      4 : Content count
- *      6 : Content IDs pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void DeleteContents(Service::Interface* self);
+        /**
+         * AM::DeleteContents service function
+         *  Inputs:
+         *      1 : MediaType
+         *    2-3 : u64, Title ID
+         *      4 : Content count
+         *      6 : Content IDs pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void DeleteContents(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetProgramList service function
- * Loads information about the desired number of titles from the desired media type into an array
- *  Inputs:
- *      1 : Title count
- *      2 : Media type to load the titles from
- *      4 : Title IDs output pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : The number of titles loaded from the requested media type
- */
-void GetProgramList(Service::Interface* self);
+        /**
+         * AM::GetProgramList service function
+         * Loads information about the desired number of titles from the desired media type into an
+         * array
+         *  Inputs:
+         *      1 : Title count
+         *      2 : Media type to load the titles from
+         *      4 : Title IDs output pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : The number of titles loaded from the requested media type
+         */
+        void GetProgramList(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetProgramInfos service function
- *  Inputs:
- *      1 : u8 Mediatype
- *      2 : Total titles
- *      4 : TitleIDList pointer
- *      6 : TitleList pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void GetProgramInfos(Service::Interface* self);
+        /**
+         * AM::GetProgramInfos service function
+         *  Inputs:
+         *      1 : u8 Mediatype
+         *      2 : Total titles
+         *      4 : TitleIDList pointer
+         *      6 : TitleList pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void GetProgramInfos(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::DeleteUserProgram service function
- * Deletes a user program
- *  Inputs:
- *      1 : Media Type
- *      2-3 : Title ID
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void DeleteUserProgram(Service::Interface* self);
+        /**
+         * AM::DeleteUserProgram service function
+         * Deletes a user program
+         *  Inputs:
+         *      1 : Media Type
+         *      2-3 : Title ID
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void DeleteUserProgram(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetDLCTitleInfos service function
- * Wrapper for AM::GetProgramInfos, explicitly checks that TID high value is 0004008C.
- *  Inputs:
- *      1 : u8 Mediatype
- *      2 : Total titles
- *      4 : TitleIDList pointer
- *      6 : TitleList pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void GetDLCTitleInfos(Service::Interface* self);
+        /**
+         * AM::GetDLCTitleInfos service function
+         * Wrapper for AM::GetProgramInfos, explicitly checks that TID high value is 0004008C.
+         *  Inputs:
+         *      1 : u8 Mediatype
+         *      2 : Total titles
+         *      4 : TitleIDList pointer
+         *      6 : TitleList pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void GetDLCTitleInfos(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetPatchTitleInfos service function
- * Wrapper for AM::GetProgramInfos, explicitly checks that TID high value is 0004000E.
- *  Inputs:
- *      1 : u8 Mediatype
- *      2 : Total titles
- *      4 : TitleIDList input pointer
- *      6 : TitleList output pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : TitleIDList input pointer
- *      4 : TitleList output pointer
- */
-void GetPatchTitleInfos(Service::Interface* self);
+        /**
+         * AM::GetPatchTitleInfos service function
+         * Wrapper for AM::GetProgramInfos, explicitly checks that TID high value is 0004000E.
+         *  Inputs:
+         *      1 : u8 Mediatype
+         *      2 : Total titles
+         *      4 : TitleIDList input pointer
+         *      6 : TitleList output pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : TitleIDList input pointer
+         *      4 : TitleList output pointer
+         */
+        void GetPatchTitleInfos(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::ListDataTitleTicketInfos service function
- *  Inputs:
- *      1 : Ticket count
- *    2-3 : u64, Title ID
- *      4 : Start Index?
- *      5 : (TicketCount * 24) << 8 | 0x4
- *      6 : Ticket Infos pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Number of ticket infos returned
- */
-void ListDataTitleTicketInfos(Service::Interface* self);
+        /**
+         * AM::ListDataTitleTicketInfos service function
+         *  Inputs:
+         *      1 : Ticket count
+         *    2-3 : u64, Title ID
+         *      4 : Start Index?
+         *      5 : (TicketCount * 24) << 8 | 0x4
+         *      6 : Ticket Infos pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Number of ticket infos returned
+         */
+        void ListDataTitleTicketInfos(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetDLCContentInfoCount service function
- * Explicitly checks that TID high value is 0004008C or an error is returned.
- *  Inputs:
- *      0 : Command header (0x100100C0)
- *      1 : MediaType
- *    2-3 : u64, Title ID
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Number of content infos plus one
- */
-void GetDLCContentInfoCount(Service::Interface* self);
+        /**
+         * AM::GetDLCContentInfoCount service function
+         * Explicitly checks that TID high value is 0004008C or an error is returned.
+         *  Inputs:
+         *      0 : Command header (0x100100C0)
+         *      1 : MediaType
+         *    2-3 : u64, Title ID
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Number of content infos plus one
+         */
+        void GetDLCContentInfoCount(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::DeleteTicket service function
- *  Inputs:
- *    1-2 : u64, Title ID
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void DeleteTicket(Service::Interface* self);
+        /**
+         * AM::DeleteTicket service function
+         *  Inputs:
+         *    1-2 : u64, Title ID
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void DeleteTicket(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetNumTickets service function
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Number of tickets
- */
-void GetNumTickets(Service::Interface* self);
+        /**
+         * AM::GetNumTickets service function
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Number of tickets
+         */
+        void GetNumTickets(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetTicketList service function
- *  Inputs:
- *      1 : Number of TicketList
- *      2 : Number to skip
- *      4 : TicketList pointer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Total TicketList
- */
-void GetTicketList(Service::Interface* self);
+        /**
+         * AM::GetTicketList service function
+         *  Inputs:
+         *      1 : Number of TicketList
+         *      2 : Number to skip
+         *      4 : TicketList pointer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Total TicketList
+         */
+        void GetTicketList(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::QueryAvailableTitleDatabase service function
- *  Inputs:
- *      1 : Media Type
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Boolean, database availability
- */
-void QueryAvailableTitleDatabase(Service::Interface* self);
+        /**
+         * AM::QueryAvailableTitleDatabase service function
+         *  Inputs:
+         *      1 : Media Type
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Boolean, database availability
+         */
+        void QueryAvailableTitleDatabase(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::CheckContentRights service function
- *  Inputs:
- *      1-2 : Title ID
- *      3 : Content Index
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Boolean, whether we have rights to this content
- */
-void CheckContentRights(Service::Interface* self);
+        /**
+         * AM::CheckContentRights service function
+         *  Inputs:
+         *      1-2 : Title ID
+         *      3 : Content Index
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Boolean, whether we have rights to this content
+         */
+        void CheckContentRights(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::CheckContentRightsIgnorePlatform service function
- *  Inputs:
- *      1-2 : Title ID
- *      3 : Content Index
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Boolean, whether we have rights to this content
- */
-void CheckContentRightsIgnorePlatform(Service::Interface* self);
+        /**
+         * AM::CheckContentRightsIgnorePlatform service function
+         *  Inputs:
+         *      1-2 : Title ID
+         *      3 : Content Index
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Boolean, whether we have rights to this content
+         */
+        void CheckContentRightsIgnorePlatform(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::BeginImportProgram service function
- * Begin importing from a CTR Installable Archive
- *  Inputs:
- *      0 : Command header (0x04020040)
- *      1 : Media type to install title to
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2-3 : CIAFile handle for application to write to
- */
-void BeginImportProgram(Service::Interface* self);
+        /**
+         * AM::BeginImportProgram service function
+         * Begin importing from a CTR Installable Archive
+         *  Inputs:
+         *      0 : Command header (0x04020040)
+         *      1 : Media type to install title to
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2-3 : CIAFile handle for application to write to
+         */
+        void BeginImportProgram(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::EndImportProgram service function
- * Finish importing from a CTR Installable Archive
- *  Inputs:
- *      0 : Command header (0x04050002)
- *      1-2 : CIAFile handle application wrote to
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void EndImportProgram(Service::Interface* self);
+        /**
+         * AM::EndImportProgram service function
+         * Finish importing from a CTR Installable Archive
+         *  Inputs:
+         *      0 : Command header (0x04050002)
+         *      1-2 : CIAFile handle application wrote to
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void EndImportProgram(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetProgramInfoFromCia service function
- * Get TitleInfo from a CIA file handle
- *  Inputs:
- *      0 : Command header (0x04080042)
- *      1 : Media type of the title
- *      2-3 : File handle CIA data can be read from
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2-8: TitleInfo structure
- */
-void GetProgramInfoFromCia(Service::Interface* self);
+        /**
+         * AM::GetProgramInfoFromCia service function
+         * Get TitleInfo from a CIA file handle
+         *  Inputs:
+         *      0 : Command header (0x04080042)
+         *      1 : Media type of the title
+         *      2-3 : File handle CIA data can be read from
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2-8: TitleInfo structure
+         */
+        void GetProgramInfoFromCia(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetSystemMenuDataFromCia service function
- * Loads a CIA file's SMDH data into a specified buffer
- *  Inputs:
- *      0 : Command header (0x04090004)
- *      1-2 : File handle CIA data can be read from
- *      3-4 : Output buffer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void GetSystemMenuDataFromCia(Service::Interface* self);
+        /**
+         * AM::GetSystemMenuDataFromCia service function
+         * Loads a CIA file's SMDH data into a specified buffer
+         *  Inputs:
+         *      0 : Command header (0x04090004)
+         *      1-2 : File handle CIA data can be read from
+         *      3-4 : Output buffer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void GetSystemMenuDataFromCia(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetDependencyListFromCia service function
- * Loads a CIA's dependency list into a specified buffer
- *  Inputs:
- *      0 : Command header (0x040A0002)
- *      1-2 : File handle CIA data can be read from
- *      64-65 : Output buffer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void GetDependencyListFromCia(Service::Interface* self);
+        /**
+         * AM::GetDependencyListFromCia service function
+         * Loads a CIA's dependency list into a specified buffer
+         *  Inputs:
+         *      0 : Command header (0x040A0002)
+         *      1-2 : File handle CIA data can be read from
+         *      64-65 : Output buffer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void GetDependencyListFromCia(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetTransferSizeFromCia service function
- * Returns the total expected transfer size up to the CIA meta offset from a CIA
- *  Inputs:
- *      0 : Command header (0x040B0002)
- *      1-2 : File handle CIA data can be read from
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2-3 : Transfer size
- */
-void GetTransferSizeFromCia(Service::Interface* self);
+        /**
+         * AM::GetTransferSizeFromCia service function
+         * Returns the total expected transfer size up to the CIA meta offset from a CIA
+         *  Inputs:
+         *      0 : Command header (0x040B0002)
+         *      1-2 : File handle CIA data can be read from
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2-3 : Transfer size
+         */
+        void GetTransferSizeFromCia(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetCoreVersionFromCia service function
- * Returns the core version from a CIA
- *  Inputs:
- *      0 : Command header (0x040C0002)
- *      1-2 : File handle CIA data can be read from
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Core version
- */
-void GetCoreVersionFromCia(Service::Interface* self);
+        /**
+         * AM::GetCoreVersionFromCia service function
+         * Returns the core version from a CIA
+         *  Inputs:
+         *      0 : Command header (0x040C0002)
+         *      1-2 : File handle CIA data can be read from
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Core version
+         */
+        void GetCoreVersionFromCia(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetRequiredSizeFromCia service function
- * Returns the required amount of free space required to install a given CIA file
- *  Inputs:
- *      0 : Command header (0x040D0042)
- *      1 : Media type to install title to
- *      2-3 : File handle CIA data can be read from
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2-3 : Required free space for CIA
- */
-void GetRequiredSizeFromCia(Service::Interface* self);
+        /**
+         * AM::GetRequiredSizeFromCia service function
+         * Returns the required amount of free space required to install a given CIA file
+         *  Inputs:
+         *      0 : Command header (0x040D0042)
+         *      1 : Media type to install title to
+         *      2-3 : File handle CIA data can be read from
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2-3 : Required free space for CIA
+         */
+        void GetRequiredSizeFromCia(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::DeleteProgram service function
- * Deletes a program
- *  Inputs:
- *      0 : Command header (0x041000C0)
- *      1 : Media type
- *      2-3 : Title ID
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void DeleteProgram(Service::Interface* self);
+        /**
+         * AM::DeleteProgram service function
+         * Deletes a program
+         *  Inputs:
+         *      0 : Command header (0x041000C0)
+         *      1 : Media type
+         *      2-3 : Title ID
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void DeleteProgram(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetMetaSizeFromCia service function
- * Returns the size of a given CIA's meta section
- *  Inputs:
- *      0 : Command header (0x04130002)
- *      1-2 : File handle CIA data can be read from
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- *      2 : Meta section size
- */
-void GetMetaSizeFromCia(Service::Interface* self);
+        /**
+         * AM::GetMetaSizeFromCia service function
+         * Returns the size of a given CIA's meta section
+         *  Inputs:
+         *      0 : Command header (0x04130002)
+         *      1-2 : File handle CIA data can be read from
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         *      2 : Meta section size
+         */
+        void GetMetaSizeFromCia(Kernel::HLERequestContext& ctx);
 
-/**
- * AM::GetMetaDataFromCia service function
- * Loads meta section data from a CIA file into a given buffer
- *  Inputs:
- *      0 : Command header (0x04140044)
- *      1-2 : File handle CIA data can be read from
- *      3-4 : Output buffer
- *  Outputs:
- *      1 : Result, 0 on success, otherwise error code
- */
-void GetMetaDataFromCia(Service::Interface* self);
+        /**
+         * AM::GetMetaDataFromCia service function
+         * Loads meta section data from a CIA file into a given buffer
+         *  Inputs:
+         *      0 : Command header (0x04140044)
+         *      1-2 : File handle CIA data can be read from
+         *      3-4 : Output buffer
+         *  Outputs:
+         *      1 : Result, 0 on success, otherwise error code
+         */
+        void GetMetaDataFromCia(Kernel::HLERequestContext& ctx);
 
-/// Initialize AM service
-void Init();
+    private:
+        std::shared_ptr<Module> am;
+    };
 
-/// Shutdown AM service
-void Shutdown();
+private:
+    /**
+     * Scans the for titles in a storage medium for listing.
+     * @param media_type the storage medium to scan
+     */
+    void ScanForTitles(Service::FS::MediaType media_type);
+
+    /**
+     * Scans all storage mediums for titles for listing.
+     */
+    void ScanForAllTitles();
+
+    bool cia_installing = false;
+    std::array<std::vector<u64_le>, 3> am_title_list;
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
 
 } // namespace AM
 } // namespace Service
diff --git a/src/core/hle/service/am/am_app.cpp b/src/core/hle/service/am/am_app.cpp
index 2be9582bb..debd64f45 100644
--- a/src/core/hle/service/am/am_app.cpp
+++ b/src/core/hle/service/am/am_app.cpp
@@ -2,30 +2,28 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include "core/hle/service/am/am.h"
 #include "core/hle/service/am/am_app.h"
 
 namespace Service {
 namespace AM {
 
-const Interface::FunctionInfo FunctionTable[] = {
-    {0x100100C0, GetDLCContentInfoCount, "GetDLCContentInfoCount"},
-    {0x10020104, FindDLCContentInfos, "FindDLCContentInfos"},
-    {0x10030142, ListDLCContentInfos, "ListDLCContentInfos"},
-    {0x10040102, DeleteContents, "DeleteContents"},
-    {0x10050084, GetDLCTitleInfos, "GetDLCTitleInfos"},
-    {0x10060080, nullptr, "GetNumDataTitleTickets"},
-    {0x10070102, ListDataTitleTicketInfos, "ListDataTitleTicketInfos"},
-    {0x100801C2, nullptr, "GetItemRights"},
-    {0x100900C0, nullptr, "IsDataTitleInUse"},
-    {0x100A0000, nullptr, "IsExternalTitleDatabaseInitialized"},
-    {0x100B00C0, nullptr, "GetNumExistingContentInfos"},
-    {0x100C0142, nullptr, "ListExistingContentInfos"},
-    {0x100D0084, GetPatchTitleInfos, "GetPatchTitleInfos"},
-};
-
-AM_APP_Interface::AM_APP_Interface() {
-    Register(FunctionTable);
+AM_APP::AM_APP(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:app", 5) {
+    static const FunctionInfo functions[] = {
+        {0x100100C0, &AM_APP::GetDLCContentInfoCount, "GetDLCContentInfoCount"},
+        {0x10020104, &AM_APP::FindDLCContentInfos, "FindDLCContentInfos"},
+        {0x10030142, &AM_APP::ListDLCContentInfos, "ListDLCContentInfos"},
+        {0x10040102, &AM_APP::DeleteContents, "DeleteContents"},
+        {0x10050084, &AM_APP::GetDLCTitleInfos, "GetDLCTitleInfos"},
+        {0x10060080, nullptr, "GetNumDataTitleTickets"},
+        {0x10070102, &AM_APP::ListDataTitleTicketInfos, "ListDataTitleTicketInfos"},
+        {0x100801C2, nullptr, "GetItemRights"},
+        {0x100900C0, nullptr, "IsDataTitleInUse"},
+        {0x100A0000, nullptr, "IsExternalTitleDatabaseInitialized"},
+        {0x100B00C0, nullptr, "GetNumExistingContentInfos"},
+        {0x100C0142, nullptr, "ListExistingContentInfos"},
+        {0x100D0084, &AM_APP::GetPatchTitleInfos, "GetPatchTitleInfos"},
+    };
+    RegisterHandlers(functions);
 }
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am_app.h b/src/core/hle/service/am/am_app.h
index fd6017d14..5edecb387 100644
--- a/src/core/hle/service/am/am_app.h
+++ b/src/core/hle/service/am/am_app.h
@@ -4,18 +4,14 @@
 
 #pragma once
 
-#include "core/hle/service/service.h"
+#include "core/hle/service/am/am.h"
 
 namespace Service {
 namespace AM {
 
-class AM_APP_Interface : public Service::Interface {
+class AM_APP final : public Module::Interface {
 public:
-    AM_APP_Interface();
-
-    std::string GetPortName() const override {
-        return "am:app";
-    }
+    explicit AM_APP(std::shared_ptr<Module> am);
 };
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am_net.cpp b/src/core/hle/service/am/am_net.cpp
index 4ba5bddf9..ca844b5b5 100644
--- a/src/core/hle/service/am/am_net.cpp
+++ b/src/core/hle/service/am/am_net.cpp
@@ -2,127 +2,125 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include "core/hle/service/am/am.h"
 #include "core/hle/service/am/am_net.h"
 
 namespace Service {
 namespace AM {
 
-const Interface::FunctionInfo FunctionTable[] = {
-    {0x00010040, GetNumPrograms, "GetNumPrograms"},
-    {0x00020082, GetProgramList, "GetProgramList"},
-    {0x00030084, GetProgramInfos, "GetProgramInfos"},
-    {0x000400C0, DeleteUserProgram, "DeleteUserProgram"},
-    {0x000500C0, nullptr, "GetProductCode"},
-    {0x000600C0, nullptr, "GetStorageId"},
-    {0x00070080, DeleteTicket, "DeleteTicket"},
-    {0x00080000, GetNumTickets, "GetNumTickets"},
-    {0x00090082, GetTicketList, "GetTicketList"},
-    {0x000A0000, nullptr, "GetDeviceID"},
-    {0x000B0040, nullptr, "GetNumImportTitleContexts"},
-    {0x000C0082, nullptr, "GetImportTitleContextList"},
-    {0x000D0084, nullptr, "GetImportTitleContexts"},
-    {0x000E00C0, nullptr, "DeleteImportTitleContext"},
-    {0x000F00C0, nullptr, "GetNumImportContentContexts"},
-    {0x00100102, nullptr, "GetImportContentContextList"},
-    {0x00110104, nullptr, "GetImportContentContexts"},
-    {0x00120102, nullptr, "DeleteImportContentContexts"},
-    {0x00130040, nullptr, "NeedsCleanup"},
-    {0x00140040, nullptr, "DoCleanup"},
-    {0x00150040, nullptr, "DeleteAllImportContexts"},
-    {0x00160000, nullptr, "DeleteAllTemporaryPrograms"},
-    {0x00170044, nullptr, "ImportTwlBackupLegacy"},
-    {0x00180080, nullptr, "InitializeTitleDatabase"},
-    {0x00190040, nullptr, "QueryAvailableTitleDatabase"},
-    {0x001A00C0, nullptr, "CalcTwlBackupSize"},
-    {0x001B0144, nullptr, "ExportTwlBackup"},
-    {0x001C0084, nullptr, "ImportTwlBackup"},
-    {0x001D0000, nullptr, "DeleteAllTwlUserPrograms"},
-    {0x001E00C8, nullptr, "ReadTwlBackupInfo"},
-    {0x001F0040, nullptr, "DeleteAllExpiredUserPrograms"},
-    {0x00200000, nullptr, "GetTwlArchiveResourceInfo"},
-    {0x00210042, nullptr, "GetPersonalizedTicketInfoList"},
-    {0x00220080, nullptr, "DeleteAllImportContextsFiltered"},
-    {0x00230080, nullptr, "GetNumImportTitleContextsFiltered"},
-    {0x002400C2, nullptr, "GetImportTitleContextListFiltered"},
-    {0x002500C0, nullptr, "CheckContentRights"},
-    {0x00260044, nullptr, "GetTicketLimitInfos"},
-    {0x00270044, nullptr, "GetDemoLaunchInfos"},
-    {0x00280108, nullptr, "ReadTwlBackupInfoEx"},
-    {0x00290082, nullptr, "DeleteUserProgramsAtomically"},
-    {0x002A00C0, nullptr, "GetNumExistingContentInfosSystem"},
-    {0x002B0142, nullptr, "ListExistingContentInfosSystem"},
-    {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"},
-    {0x002D00C0, nullptr, "CheckContentRightsIgnorePlatform"},
-    {0x04010080, nullptr, "UpdateFirmwareTo"},
-    {0x04020040, BeginImportProgram, "BeginImportProgram"},
-    {0x04030000, nullptr, "BeginImportProgramTemporarily"},
-    {0x04040002, nullptr, "CancelImportProgram"},
-    {0x04050002, EndImportProgram, "EndImportProgram"},
-    {0x04060002, nullptr, "EndImportProgramWithoutCommit"},
-    {0x040700C2, nullptr, "CommitImportPrograms"},
-    {0x04080042, GetProgramInfoFromCia, "GetProgramInfoFromCia"},
-    {0x04090004, GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
-    {0x040A0002, GetDependencyListFromCia, "GetDependencyListFromCia"},
-    {0x040B0002, GetTransferSizeFromCia, "GetTransferSizeFromCia"},
-    {0x040C0002, GetCoreVersionFromCia, "GetCoreVersionFromCia"},
-    {0x040D0042, GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
-    {0x040E00C2, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
-    {0x040F0000, nullptr, "UpdateFirmwareAuto"},
-    {0x041000C0, DeleteProgram, "DeleteProgram"},
-    {0x04110044, nullptr, "GetTwlProgramListForReboot"},
-    {0x04120000, nullptr, "GetSystemUpdaterMutex"},
-    {0x04130002, GetMetaSizeFromCia, "GetMetaSizeFromCia"},
-    {0x04140044, GetMetaDataFromCia, "GetMetaDataFromCia"},
-    {0x04150080, nullptr, "CheckDemoLaunchRights"},
-    {0x041600C0, nullptr, "GetInternalTitleLocationInfo"},
-    {0x041700C0, nullptr, "PerpetuateAgbSaveData"},
-    {0x04180040, nullptr, "BeginImportProgramForOverWrite"},
-    {0x04190000, nullptr, "BeginImportSystemProgram"},
-    {0x08010000, nullptr, "BeginImportTicket"},
-    {0x08020002, nullptr, "CancelImportTicket"},
-    {0x08030002, nullptr, "EndImportTicket"},
-    {0x08040100, nullptr, "BeginImportTitle"},
-    {0x08050000, nullptr, "StopImportTitle"},
-    {0x080600C0, nullptr, "ResumeImportTitle"},
-    {0x08070000, nullptr, "CancelImportTitle"},
-    {0x08080000, nullptr, "EndImportTitle"},
-    {0x080900C2, nullptr, "CommitImportTitles"},
-    {0x080A0000, nullptr, "BeginImportTmd"},
-    {0x080B0002, nullptr, "CancelImportTmd"},
-    {0x080C0042, nullptr, "EndImportTmd"},
-    {0x080D0042, nullptr, "CreateImportContentContexts"},
-    {0x080E0040, nullptr, "BeginImportContent"},
-    {0x080F0002, nullptr, "StopImportContent"},
-    {0x08100040, nullptr, "ResumeImportContent"},
-    {0x08110002, nullptr, "CancelImportContent"},
-    {0x08120002, nullptr, "EndImportContent"},
-    {0x08130000, nullptr, "GetNumCurrentImportContentContexts"},
-    {0x08140042, nullptr, "GetCurrentImportContentContextList"},
-    {0x08150044, nullptr, "GetCurrentImportContentContexts"},
-    {0x08160146, nullptr, "Sign"},
-    {0x08170146, nullptr, "Verify"},
-    {0x08180042, nullptr, "GetDeviceCert"},
-    {0x08190108, nullptr, "ImportCertificates"},
-    {0x081A0042, nullptr, "ImportCertificate"},
-    {0x081B00C2, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},
-    {0x081C0100, nullptr, "DeleteTicketId"},
-    {0x081D0080, nullptr, "GetNumTicketIds"},
-    {0x081E0102, nullptr, "GetTicketIdList"},
-    {0x081F0080, nullptr, "GetNumTicketsOfProgram"},
-    {0x08200102, nullptr, "ListTicketInfos"},
-    {0x08210142, nullptr, "GetRightsOnlyTicketData"},
-    {0x08220000, nullptr, "GetNumCurrentContentInfos"},
-    {0x08230044, nullptr, "FindCurrentContentInfos"},
-    {0x08240082, nullptr, "ListCurrentContentInfos"},
-    {0x08250102, nullptr, "CalculateContextRequiredSize"},
-    {0x08260042, nullptr, "UpdateImportContentContexts"},
-    {0x08270000, nullptr, "DeleteAllDemoLaunchInfos"},
-    {0x082800C0, nullptr, "BeginImportTitleForOverWrite"},
-};
-
-AM_NET_Interface::AM_NET_Interface() {
-    Register(FunctionTable);
+AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:net", 5) {
+    static const FunctionInfo functions[] = {
+        {0x00010040, &AM_NET::GetNumPrograms, "GetNumPrograms"},
+        {0x00020082, &AM_NET::GetProgramList, "GetProgramList"},
+        {0x00030084, &AM_NET::GetProgramInfos, "GetProgramInfos"},
+        {0x000400C0, &AM_NET::DeleteUserProgram, "DeleteUserProgram"},
+        {0x000500C0, nullptr, "GetProductCode"},
+        {0x000600C0, nullptr, "GetStorageId"},
+        {0x00070080, &AM_NET::DeleteTicket, "DeleteTicket"},
+        {0x00080000, &AM_NET::GetNumTickets, "GetNumTickets"},
+        {0x00090082, &AM_NET::GetTicketList, "GetTicketList"},
+        {0x000A0000, nullptr, "GetDeviceID"},
+        {0x000B0040, nullptr, "GetNumImportTitleContexts"},
+        {0x000C0082, nullptr, "GetImportTitleContextList"},
+        {0x000D0084, nullptr, "GetImportTitleContexts"},
+        {0x000E00C0, nullptr, "DeleteImportTitleContext"},
+        {0x000F00C0, nullptr, "GetNumImportContentContexts"},
+        {0x00100102, nullptr, "GetImportContentContextList"},
+        {0x00110104, nullptr, "GetImportContentContexts"},
+        {0x00120102, nullptr, "DeleteImportContentContexts"},
+        {0x00130040, nullptr, "NeedsCleanup"},
+        {0x00140040, nullptr, "DoCleanup"},
+        {0x00150040, nullptr, "DeleteAllImportContexts"},
+        {0x00160000, nullptr, "DeleteAllTemporaryPrograms"},
+        {0x00170044, nullptr, "ImportTwlBackupLegacy"},
+        {0x00180080, nullptr, "InitializeTitleDatabase"},
+        {0x00190040, nullptr, "QueryAvailableTitleDatabase"},
+        {0x001A00C0, nullptr, "CalcTwlBackupSize"},
+        {0x001B0144, nullptr, "ExportTwlBackup"},
+        {0x001C0084, nullptr, "ImportTwlBackup"},
+        {0x001D0000, nullptr, "DeleteAllTwlUserPrograms"},
+        {0x001E00C8, nullptr, "ReadTwlBackupInfo"},
+        {0x001F0040, nullptr, "DeleteAllExpiredUserPrograms"},
+        {0x00200000, nullptr, "GetTwlArchiveResourceInfo"},
+        {0x00210042, nullptr, "GetPersonalizedTicketInfoList"},
+        {0x00220080, nullptr, "DeleteAllImportContextsFiltered"},
+        {0x00230080, nullptr, "GetNumImportTitleContextsFiltered"},
+        {0x002400C2, nullptr, "GetImportTitleContextListFiltered"},
+        {0x002500C0, nullptr, "CheckContentRights"},
+        {0x00260044, nullptr, "GetTicketLimitInfos"},
+        {0x00270044, nullptr, "GetDemoLaunchInfos"},
+        {0x00280108, nullptr, "ReadTwlBackupInfoEx"},
+        {0x00290082, nullptr, "DeleteUserProgramsAtomically"},
+        {0x002A00C0, nullptr, "GetNumExistingContentInfosSystem"},
+        {0x002B0142, nullptr, "ListExistingContentInfosSystem"},
+        {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"},
+        {0x002D00C0, nullptr, "CheckContentRightsIgnorePlatform"},
+        {0x04010080, nullptr, "UpdateFirmwareTo"},
+        {0x04020040, &AM_NET::BeginImportProgram, "BeginImportProgram"},
+        {0x04030000, nullptr, "BeginImportProgramTemporarily"},
+        {0x04040002, nullptr, "CancelImportProgram"},
+        {0x04050002, &AM_NET::EndImportProgram, "EndImportProgram"},
+        {0x04060002, nullptr, "EndImportProgramWithoutCommit"},
+        {0x040700C2, nullptr, "CommitImportPrograms"},
+        {0x04080042, &AM_NET::GetProgramInfoFromCia, "GetProgramInfoFromCia"},
+        {0x04090004, &AM_NET::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
+        {0x040A0002, &AM_NET::GetDependencyListFromCia, "GetDependencyListFromCia"},
+        {0x040B0002, &AM_NET::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
+        {0x040C0002, &AM_NET::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
+        {0x040D0042, &AM_NET::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
+        {0x040E00C2, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
+        {0x040F0000, nullptr, "UpdateFirmwareAuto"},
+        {0x041000C0, &AM_NET::DeleteProgram, "DeleteProgram"},
+        {0x04110044, nullptr, "GetTwlProgramListForReboot"},
+        {0x04120000, nullptr, "GetSystemUpdaterMutex"},
+        {0x04130002, &AM_NET::GetMetaSizeFromCia, "GetMetaSizeFromCia"},
+        {0x04140044, &AM_NET::GetMetaDataFromCia, "GetMetaDataFromCia"},
+        {0x04150080, nullptr, "CheckDemoLaunchRights"},
+        {0x041600C0, nullptr, "GetInternalTitleLocationInfo"},
+        {0x041700C0, nullptr, "PerpetuateAgbSaveData"},
+        {0x04180040, nullptr, "BeginImportProgramForOverWrite"},
+        {0x04190000, nullptr, "BeginImportSystemProgram"},
+        {0x08010000, nullptr, "BeginImportTicket"},
+        {0x08020002, nullptr, "CancelImportTicket"},
+        {0x08030002, nullptr, "EndImportTicket"},
+        {0x08040100, nullptr, "BeginImportTitle"},
+        {0x08050000, nullptr, "StopImportTitle"},
+        {0x080600C0, nullptr, "ResumeImportTitle"},
+        {0x08070000, nullptr, "CancelImportTitle"},
+        {0x08080000, nullptr, "EndImportTitle"},
+        {0x080900C2, nullptr, "CommitImportTitles"},
+        {0x080A0000, nullptr, "BeginImportTmd"},
+        {0x080B0002, nullptr, "CancelImportTmd"},
+        {0x080C0042, nullptr, "EndImportTmd"},
+        {0x080D0042, nullptr, "CreateImportContentContexts"},
+        {0x080E0040, nullptr, "BeginImportContent"},
+        {0x080F0002, nullptr, "StopImportContent"},
+        {0x08100040, nullptr, "ResumeImportContent"},
+        {0x08110002, nullptr, "CancelImportContent"},
+        {0x08120002, nullptr, "EndImportContent"},
+        {0x08130000, nullptr, "GetNumCurrentImportContentContexts"},
+        {0x08140042, nullptr, "GetCurrentImportContentContextList"},
+        {0x08150044, nullptr, "GetCurrentImportContentContexts"},
+        {0x08160146, nullptr, "Sign"},
+        {0x08170146, nullptr, "Verify"},
+        {0x08180042, nullptr, "GetDeviceCert"},
+        {0x08190108, nullptr, "ImportCertificates"},
+        {0x081A0042, nullptr, "ImportCertificate"},
+        {0x081B00C2, nullptr, "CommitImportTitlesAndUpdateFirmwareAuto"},
+        {0x081C0100, nullptr, "DeleteTicketId"},
+        {0x081D0080, nullptr, "GetNumTicketIds"},
+        {0x081E0102, nullptr, "GetTicketIdList"},
+        {0x081F0080, nullptr, "GetNumTicketsOfProgram"},
+        {0x08200102, nullptr, "ListTicketInfos"},
+        {0x08210142, nullptr, "GetRightsOnlyTicketData"},
+        {0x08220000, nullptr, "GetNumCurrentContentInfos"},
+        {0x08230044, nullptr, "FindCurrentContentInfos"},
+        {0x08240082, nullptr, "ListCurrentContentInfos"},
+        {0x08250102, nullptr, "CalculateContextRequiredSize"},
+        {0x08260042, nullptr, "UpdateImportContentContexts"},
+        {0x08270000, nullptr, "DeleteAllDemoLaunchInfos"},
+        {0x082800C0, nullptr, "BeginImportTitleForOverWrite"},
+    };
+    RegisterHandlers(functions);
 }
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am_net.h b/src/core/hle/service/am/am_net.h
index 25d2c3f23..d557cf01d 100644
--- a/src/core/hle/service/am/am_net.h
+++ b/src/core/hle/service/am/am_net.h
@@ -4,18 +4,14 @@
 
 #pragma once
 
-#include "core/hle/service/service.h"
+#include "core/hle/service/am/am.h"
 
 namespace Service {
 namespace AM {
 
-class AM_NET_Interface : public Service::Interface {
+class AM_NET final : public Module::Interface {
 public:
-    AM_NET_Interface();
-
-    std::string GetPortName() const override {
-        return "am:net";
-    }
+    explicit AM_NET(std::shared_ptr<Module> am);
 };
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am_sys.cpp b/src/core/hle/service/am/am_sys.cpp
index 7b2eebf17..61a2bcc7c 100644
--- a/src/core/hle/service/am/am_sys.cpp
+++ b/src/core/hle/service/am/am_sys.cpp
@@ -2,75 +2,73 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include "core/hle/service/am/am.h"
 #include "core/hle/service/am/am_sys.h"
 
 namespace Service {
 namespace AM {
 
-const Interface::FunctionInfo FunctionTable[] = {
-    {0x00010040, GetNumPrograms, "GetNumPrograms"},
-    {0x00020082, GetProgramList, "GetProgramList"},
-    {0x00030084, GetProgramInfos, "GetProgramInfos"},
-    {0x000400C0, DeleteUserProgram, "DeleteUserProgram"},
-    {0x000500C0, nullptr, "GetProductCode"},
-    {0x000600C0, nullptr, "GetStorageId"},
-    {0x00070080, DeleteTicket, "DeleteTicket"},
-    {0x00080000, GetNumTickets, "GetNumTickets"},
-    {0x00090082, GetTicketList, "GetTicketList"},
-    {0x000A0000, nullptr, "GetDeviceID"},
-    {0x000B0040, nullptr, "GetNumImportTitleContexts"},
-    {0x000C0082, nullptr, "GetImportTitleContextList"},
-    {0x000D0084, nullptr, "GetImportTitleContexts"},
-    {0x000E00C0, nullptr, "DeleteImportTitleContext"},
-    {0x000F00C0, nullptr, "GetNumImportContentContexts"},
-    {0x00100102, nullptr, "GetImportContentContextList"},
-    {0x00110104, nullptr, "GetImportContentContexts"},
-    {0x00120102, nullptr, "DeleteImportContentContexts"},
-    {0x00130040, nullptr, "NeedsCleanup"},
-    {0x00140040, nullptr, "DoCleanup"},
-    {0x00150040, nullptr, "DeleteAllImportContexts"},
-    {0x00160000, nullptr, "DeleteAllTemporaryPrograms"},
-    {0x00170044, nullptr, "ImportTwlBackupLegacy"},
-    {0x00180080, nullptr, "InitializeTitleDatabase"},
-    {0x00190040, QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
-    {0x001A00C0, nullptr, "CalcTwlBackupSize"},
-    {0x001B0144, nullptr, "ExportTwlBackup"},
-    {0x001C0084, nullptr, "ImportTwlBackup"},
-    {0x001D0000, nullptr, "DeleteAllTwlUserPrograms"},
-    {0x001E00C8, nullptr, "ReadTwlBackupInfo"},
-    {0x001F0040, nullptr, "DeleteAllExpiredUserPrograms"},
-    {0x00200000, nullptr, "GetTwlArchiveResourceInfo"},
-    {0x00210042, nullptr, "GetPersonalizedTicketInfoList"},
-    {0x00220080, nullptr, "DeleteAllImportContextsFiltered"},
-    {0x00230080, nullptr, "GetNumImportTitleContextsFiltered"},
-    {0x002400C2, nullptr, "GetImportTitleContextListFiltered"},
-    {0x002500C0, CheckContentRights, "CheckContentRights"},
-    {0x00260044, nullptr, "GetTicketLimitInfos"},
-    {0x00270044, nullptr, "GetDemoLaunchInfos"},
-    {0x00280108, nullptr, "ReadTwlBackupInfoEx"},
-    {0x00290082, nullptr, "DeleteUserProgramsAtomically"},
-    {0x002A00C0, nullptr, "GetNumExistingContentInfosSystem"},
-    {0x002B0142, nullptr, "ListExistingContentInfosSystem"},
-    {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"},
-    {0x002D00C0, CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
-    {0x100100C0, GetDLCContentInfoCount, "GetDLCContentInfoCount"},
-    {0x10020104, FindDLCContentInfos, "FindDLCContentInfos"},
-    {0x10030142, ListDLCContentInfos, "ListDLCContentInfos"},
-    {0x10040102, DeleteContents, "DeleteContents"},
-    {0x10050084, GetDLCTitleInfos, "GetDLCTitleInfos"},
-    {0x10060080, nullptr, "GetNumDataTitleTickets"},
-    {0x10070102, ListDataTitleTicketInfos, "ListDataTitleTicketInfos"},
-    {0x100801C2, nullptr, "GetItemRights"},
-    {0x100900C0, nullptr, "IsDataTitleInUse"},
-    {0x100A0000, nullptr, "IsExternalTitleDatabaseInitialized"},
-    {0x100B00C0, nullptr, "GetNumExistingContentInfos"},
-    {0x100C0142, nullptr, "ListExistingContentInfos"},
-    {0x100D0084, GetPatchTitleInfos, "GetPatchTitleInfos"},
-};
-
-AM_SYS_Interface::AM_SYS_Interface() {
-    Register(FunctionTable);
+AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:sys", 5) {
+    static const FunctionInfo functions[] = {
+        {0x00010040, &AM_SYS::GetNumPrograms, "GetNumPrograms"},
+        {0x00020082, &AM_SYS::GetProgramList, "GetProgramList"},
+        {0x00030084, &AM_SYS::GetProgramInfos, "GetProgramInfos"},
+        {0x000400C0, &AM_SYS::DeleteUserProgram, "DeleteUserProgram"},
+        {0x000500C0, nullptr, "GetProductCode"},
+        {0x000600C0, nullptr, "GetStorageId"},
+        {0x00070080, &AM_SYS::DeleteTicket, "DeleteTicket"},
+        {0x00080000, &AM_SYS::GetNumTickets, "GetNumTickets"},
+        {0x00090082, &AM_SYS::GetTicketList, "GetTicketList"},
+        {0x000A0000, nullptr, "GetDeviceID"},
+        {0x000B0040, nullptr, "GetNumImportTitleContexts"},
+        {0x000C0082, nullptr, "GetImportTitleContextList"},
+        {0x000D0084, nullptr, "GetImportTitleContexts"},
+        {0x000E00C0, nullptr, "DeleteImportTitleContext"},
+        {0x000F00C0, nullptr, "GetNumImportContentContexts"},
+        {0x00100102, nullptr, "GetImportContentContextList"},
+        {0x00110104, nullptr, "GetImportContentContexts"},
+        {0x00120102, nullptr, "DeleteImportContentContexts"},
+        {0x00130040, nullptr, "NeedsCleanup"},
+        {0x00140040, nullptr, "DoCleanup"},
+        {0x00150040, nullptr, "DeleteAllImportContexts"},
+        {0x00160000, nullptr, "DeleteAllTemporaryPrograms"},
+        {0x00170044, nullptr, "ImportTwlBackupLegacy"},
+        {0x00180080, nullptr, "InitializeTitleDatabase"},
+        {0x00190040, &AM_SYS::QueryAvailableTitleDatabase, "QueryAvailableTitleDatabase"},
+        {0x001A00C0, nullptr, "CalcTwlBackupSize"},
+        {0x001B0144, nullptr, "ExportTwlBackup"},
+        {0x001C0084, nullptr, "ImportTwlBackup"},
+        {0x001D0000, nullptr, "DeleteAllTwlUserPrograms"},
+        {0x001E00C8, nullptr, "ReadTwlBackupInfo"},
+        {0x001F0040, nullptr, "DeleteAllExpiredUserPrograms"},
+        {0x00200000, nullptr, "GetTwlArchiveResourceInfo"},
+        {0x00210042, nullptr, "GetPersonalizedTicketInfoList"},
+        {0x00220080, nullptr, "DeleteAllImportContextsFiltered"},
+        {0x00230080, nullptr, "GetNumImportTitleContextsFiltered"},
+        {0x002400C2, nullptr, "GetImportTitleContextListFiltered"},
+        {0x002500C0, &AM_SYS::CheckContentRights, "CheckContentRights"},
+        {0x00260044, nullptr, "GetTicketLimitInfos"},
+        {0x00270044, nullptr, "GetDemoLaunchInfos"},
+        {0x00280108, nullptr, "ReadTwlBackupInfoEx"},
+        {0x00290082, nullptr, "DeleteUserProgramsAtomically"},
+        {0x002A00C0, nullptr, "GetNumExistingContentInfosSystem"},
+        {0x002B0142, nullptr, "ListExistingContentInfosSystem"},
+        {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"},
+        {0x002D00C0, &AM_SYS::CheckContentRightsIgnorePlatform, "CheckContentRightsIgnorePlatform"},
+        {0x100100C0, &AM_SYS::GetDLCContentInfoCount, "GetDLCContentInfoCount"},
+        {0x10020104, &AM_SYS::FindDLCContentInfos, "FindDLCContentInfos"},
+        {0x10030142, &AM_SYS::ListDLCContentInfos, "ListDLCContentInfos"},
+        {0x10040102, &AM_SYS::DeleteContents, "DeleteContents"},
+        {0x10050084, &AM_SYS::GetDLCTitleInfos, "GetDLCTitleInfos"},
+        {0x10060080, nullptr, "GetNumDataTitleTickets"},
+        {0x10070102, &AM_SYS::ListDataTitleTicketInfos, "ListDataTitleTicketInfos"},
+        {0x100801C2, nullptr, "GetItemRights"},
+        {0x100900C0, nullptr, "IsDataTitleInUse"},
+        {0x100A0000, nullptr, "IsExternalTitleDatabaseInitialized"},
+        {0x100B00C0, nullptr, "GetNumExistingContentInfos"},
+        {0x100C0142, nullptr, "ListExistingContentInfos"},
+        {0x100D0084, &AM_SYS::GetPatchTitleInfos, "GetPatchTitleInfos"},
+    };
+    RegisterHandlers(functions);
 }
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am_sys.h b/src/core/hle/service/am/am_sys.h
index b114f1d35..7d38dfc98 100644
--- a/src/core/hle/service/am/am_sys.h
+++ b/src/core/hle/service/am/am_sys.h
@@ -4,18 +4,14 @@
 
 #pragma once
 
-#include "core/hle/service/service.h"
+#include "core/hle/service/am/am.h"
 
 namespace Service {
 namespace AM {
 
-class AM_SYS_Interface : public Service::Interface {
+class AM_SYS final : public Module::Interface {
 public:
-    AM_SYS_Interface();
-
-    std::string GetPortName() const override {
-        return "am:sys";
-    }
+    explicit AM_SYS(std::shared_ptr<Module> am);
 };
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am_u.cpp b/src/core/hle/service/am/am_u.cpp
index dc9b6985a..9d3d26872 100644
--- a/src/core/hle/service/am/am_u.cpp
+++ b/src/core/hle/service/am/am_u.cpp
@@ -2,87 +2,85 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
-#include "core/hle/service/am/am.h"
 #include "core/hle/service/am/am_u.h"
 
 namespace Service {
 namespace AM {
 
-const Interface::FunctionInfo FunctionTable[] = {
-    {0x00010040, GetNumPrograms, "GetNumPrograms"},
-    {0x00020082, GetProgramList, "GetProgramList"},
-    {0x00030084, GetProgramInfos, "GetProgramInfos"},
-    {0x000400C0, DeleteUserProgram, "DeleteUserProgram"},
-    {0x000500C0, nullptr, "GetProductCode"},
-    {0x000600C0, nullptr, "GetStorageId"},
-    {0x00070080, DeleteTicket, "DeleteTicket"},
-    {0x00080000, GetNumTickets, "GetNumTickets"},
-    {0x00090082, GetTicketList, "GetTicketList"},
-    {0x000A0000, nullptr, "GetDeviceID"},
-    {0x000B0040, nullptr, "GetNumImportTitleContexts"},
-    {0x000C0082, nullptr, "GetImportTitleContextList"},
-    {0x000D0084, nullptr, "GetImportTitleContexts"},
-    {0x000E00C0, nullptr, "DeleteImportTitleContext"},
-    {0x000F00C0, nullptr, "GetNumImportContentContexts"},
-    {0x00100102, nullptr, "GetImportContentContextList"},
-    {0x00110104, nullptr, "GetImportContentContexts"},
-    {0x00120102, nullptr, "DeleteImportContentContexts"},
-    {0x00130040, nullptr, "NeedsCleanup"},
-    {0x00140040, nullptr, "DoCleanup"},
-    {0x00150040, nullptr, "DeleteAllImportContexts"},
-    {0x00160000, nullptr, "DeleteAllTemporaryPrograms"},
-    {0x00170044, nullptr, "ImportTwlBackupLegacy"},
-    {0x00180080, nullptr, "InitializeTitleDatabase"},
-    {0x00190040, nullptr, "QueryAvailableTitleDatabase"},
-    {0x001A00C0, nullptr, "CalcTwlBackupSize"},
-    {0x001B0144, nullptr, "ExportTwlBackup"},
-    {0x001C0084, nullptr, "ImportTwlBackup"},
-    {0x001D0000, nullptr, "DeleteAllTwlUserPrograms"},
-    {0x001E00C8, nullptr, "ReadTwlBackupInfo"},
-    {0x001F0040, nullptr, "DeleteAllExpiredUserPrograms"},
-    {0x00200000, nullptr, "GetTwlArchiveResourceInfo"},
-    {0x00210042, nullptr, "GetPersonalizedTicketInfoList"},
-    {0x00220080, nullptr, "DeleteAllImportContextsFiltered"},
-    {0x00230080, nullptr, "GetNumImportTitleContextsFiltered"},
-    {0x002400C2, nullptr, "GetImportTitleContextListFiltered"},
-    {0x002500C0, nullptr, "CheckContentRights"},
-    {0x00260044, nullptr, "GetTicketLimitInfos"},
-    {0x00270044, nullptr, "GetDemoLaunchInfos"},
-    {0x00280108, nullptr, "ReadTwlBackupInfoEx"},
-    {0x00290082, nullptr, "DeleteUserProgramsAtomically"},
-    {0x002A00C0, nullptr, "GetNumExistingContentInfosSystem"},
-    {0x002B0142, nullptr, "ListExistingContentInfosSystem"},
-    {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"},
-    {0x002D00C0, nullptr, "CheckContentRightsIgnorePlatform"},
-    {0x04010080, nullptr, "UpdateFirmwareTo"},
-    {0x04020040, BeginImportProgram, "BeginImportProgram"},
-    {0x04030000, nullptr, "BeginImportProgramTemporarily"},
-    {0x04040002, nullptr, "CancelImportProgram"},
-    {0x04050002, EndImportProgram, "EndImportProgram"},
-    {0x04060002, nullptr, "EndImportProgramWithoutCommit"},
-    {0x040700C2, nullptr, "CommitImportPrograms"},
-    {0x04080042, GetProgramInfoFromCia, "GetProgramInfoFromCia"},
-    {0x04090004, GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
-    {0x040A0002, GetDependencyListFromCia, "GetDependencyListFromCia"},
-    {0x040B0002, GetTransferSizeFromCia, "GetTransferSizeFromCia"},
-    {0x040C0002, GetCoreVersionFromCia, "GetCoreVersionFromCia"},
-    {0x040D0042, GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
-    {0x040E00C2, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
-    {0x040F0000, nullptr, "UpdateFirmwareAuto"},
-    {0x041000C0, DeleteProgram, "DeleteProgram"},
-    {0x04110044, nullptr, "GetTwlProgramListForReboot"},
-    {0x04120000, nullptr, "GetSystemUpdaterMutex"},
-    {0x04130002, GetMetaSizeFromCia, "GetMetaSizeFromCia"},
-    {0x04140044, GetMetaDataFromCia, "GetMetaDataFromCia"},
-    {0x04150080, nullptr, "CheckDemoLaunchRights"},
-    {0x041600C0, nullptr, "GetInternalTitleLocationInfo"},
-    {0x041700C0, nullptr, "PerpetuateAgbSaveData"},
-    {0x04180040, nullptr, "BeginImportProgramForOverWrite"},
-    {0x04190000, nullptr, "BeginImportSystemProgram"},
-};
-
-AM_U_Interface::AM_U_Interface() {
-    Register(FunctionTable);
+AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u", 5) {
+    static const FunctionInfo functions[] = {
+        {0x00010040, &AM_U::GetNumPrograms, "GetNumPrograms"},
+        {0x00020082, &AM_U::GetProgramList, "GetProgramList"},
+        {0x00030084, &AM_U::GetProgramInfos, "GetProgramInfos"},
+        {0x000400C0, &AM_U::DeleteUserProgram, "DeleteUserProgram"},
+        {0x000500C0, nullptr, "GetProductCode"},
+        {0x000600C0, nullptr, "GetStorageId"},
+        {0x00070080, &AM_U::DeleteTicket, "DeleteTicket"},
+        {0x00080000, &AM_U::GetNumTickets, "GetNumTickets"},
+        {0x00090082, &AM_U::GetTicketList, "GetTicketList"},
+        {0x000A0000, nullptr, "GetDeviceID"},
+        {0x000B0040, nullptr, "GetNumImportTitleContexts"},
+        {0x000C0082, nullptr, "GetImportTitleContextList"},
+        {0x000D0084, nullptr, "GetImportTitleContexts"},
+        {0x000E00C0, nullptr, "DeleteImportTitleContext"},
+        {0x000F00C0, nullptr, "GetNumImportContentContexts"},
+        {0x00100102, nullptr, "GetImportContentContextList"},
+        {0x00110104, nullptr, "GetImportContentContexts"},
+        {0x00120102, nullptr, "DeleteImportContentContexts"},
+        {0x00130040, nullptr, "NeedsCleanup"},
+        {0x00140040, nullptr, "DoCleanup"},
+        {0x00150040, nullptr, "DeleteAllImportContexts"},
+        {0x00160000, nullptr, "DeleteAllTemporaryPrograms"},
+        {0x00170044, nullptr, "ImportTwlBackupLegacy"},
+        {0x00180080, nullptr, "InitializeTitleDatabase"},
+        {0x00190040, nullptr, "QueryAvailableTitleDatabase"},
+        {0x001A00C0, nullptr, "CalcTwlBackupSize"},
+        {0x001B0144, nullptr, "ExportTwlBackup"},
+        {0x001C0084, nullptr, "ImportTwlBackup"},
+        {0x001D0000, nullptr, "DeleteAllTwlUserPrograms"},
+        {0x001E00C8, nullptr, "ReadTwlBackupInfo"},
+        {0x001F0040, nullptr, "DeleteAllExpiredUserPrograms"},
+        {0x00200000, nullptr, "GetTwlArchiveResourceInfo"},
+        {0x00210042, nullptr, "GetPersonalizedTicketInfoList"},
+        {0x00220080, nullptr, "DeleteAllImportContextsFiltered"},
+        {0x00230080, nullptr, "GetNumImportTitleContextsFiltered"},
+        {0x002400C2, nullptr, "GetImportTitleContextListFiltered"},
+        {0x002500C0, nullptr, "CheckContentRights"},
+        {0x00260044, nullptr, "GetTicketLimitInfos"},
+        {0x00270044, nullptr, "GetDemoLaunchInfos"},
+        {0x00280108, nullptr, "ReadTwlBackupInfoEx"},
+        {0x00290082, nullptr, "DeleteUserProgramsAtomically"},
+        {0x002A00C0, nullptr, "GetNumExistingContentInfosSystem"},
+        {0x002B0142, nullptr, "ListExistingContentInfosSystem"},
+        {0x002C0084, nullptr, "GetProgramInfosIgnorePlatform"},
+        {0x002D00C0, nullptr, "CheckContentRightsIgnorePlatform"},
+        {0x04010080, nullptr, "UpdateFirmwareTo"},
+        {0x04020040, &AM_U::BeginImportProgram, "BeginImportProgram"},
+        {0x04030000, nullptr, "BeginImportProgramTemporarily"},
+        {0x04040002, nullptr, "CancelImportProgram"},
+        {0x04050002, &AM_U::EndImportProgram, "EndImportProgram"},
+        {0x04060002, nullptr, "EndImportProgramWithoutCommit"},
+        {0x040700C2, nullptr, "CommitImportPrograms"},
+        {0x04080042, &AM_U::GetProgramInfoFromCia, "GetProgramInfoFromCia"},
+        {0x04090004, &AM_U::GetSystemMenuDataFromCia, "GetSystemMenuDataFromCia"},
+        {0x040A0002, &AM_U::GetDependencyListFromCia, "GetDependencyListFromCia"},
+        {0x040B0002, &AM_U::GetTransferSizeFromCia, "GetTransferSizeFromCia"},
+        {0x040C0002, &AM_U::GetCoreVersionFromCia, "GetCoreVersionFromCia"},
+        {0x040D0042, &AM_U::GetRequiredSizeFromCia, "GetRequiredSizeFromCia"},
+        {0x040E00C2, nullptr, "CommitImportProgramsAndUpdateFirmwareAuto"},
+        {0x040F0000, nullptr, "UpdateFirmwareAuto"},
+        {0x041000C0, &AM_U::DeleteProgram, "DeleteProgram"},
+        {0x04110044, nullptr, "GetTwlProgramListForReboot"},
+        {0x04120000, nullptr, "GetSystemUpdaterMutex"},
+        {0x04130002, &AM_U::GetMetaSizeFromCia, "GetMetaSizeFromCia"},
+        {0x04140044, &AM_U::GetMetaDataFromCia, "GetMetaDataFromCia"},
+        {0x04150080, nullptr, "CheckDemoLaunchRights"},
+        {0x041600C0, nullptr, "GetInternalTitleLocationInfo"},
+        {0x041700C0, nullptr, "PerpetuateAgbSaveData"},
+        {0x04180040, nullptr, "BeginImportProgramForOverWrite"},
+        {0x04190000, nullptr, "BeginImportSystemProgram"},
+    };
+    RegisterHandlers(functions);
 }
 
 } // namespace AM
diff --git a/src/core/hle/service/am/am_u.h b/src/core/hle/service/am/am_u.h
index 3b2454b6c..92a797ca0 100644
--- a/src/core/hle/service/am/am_u.h
+++ b/src/core/hle/service/am/am_u.h
@@ -4,18 +4,14 @@
 
 #pragma once
 
-#include "core/hle/service/service.h"
+#include "core/hle/service/am/am.h"
 
 namespace Service {
 namespace AM {
 
-class AM_U_Interface : public Service::Interface {
+class AM_U final : public Module::Interface {
 public:
-    AM_U_Interface();
-
-    std::string GetPortName() const override {
-        return "am:u";
-    }
+    explicit AM_U(std::shared_ptr<Module> am);
 };
 
 } // namespace AM
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 045968065..36d2f3da3 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -237,7 +237,7 @@ void Init() {
     FS::InstallInterfaces(*SM::g_service_manager);
     FS::ArchiveInit();
     ACT::Init();
-    AM::Init();
+    AM::InstallInterfaces(*SM::g_service_manager);
     APT::InstallInterfaces(*SM::g_service_manager);
     BOSS::Init();
     CAM::InstallInterfaces(*SM::g_service_manager);
@@ -280,7 +280,6 @@ void Shutdown() {
     CFG::Shutdown();
     CECD::Shutdown();
     BOSS::Shutdown();
-    AM::Shutdown();
     FS::ArchiveShutdown();
 
     SM::g_service_manager = nullptr;