From 07acc6ceab8322518c480e6e7dcedf07093eee95 Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Wed, 6 Dec 2017 16:53:03 +0200
Subject: [PATCH] cam: convert to ServiceFramework

---
 src/core/hle/service/cam/cam.cpp   | 448 ++++++--------
 src/core/hle/service/cam/cam.h     | 948 ++++++++++++++++-------------
 src/core/hle/service/cam/cam_c.cpp |  75 ++-
 src/core/hle/service/cam/cam_c.h   |  10 +-
 src/core/hle/service/cam/cam_q.cpp |   9 +-
 src/core/hle/service/cam/cam_q.h   |   8 +-
 src/core/hle/service/cam/cam_s.cpp |  75 ++-
 src/core/hle/service/cam/cam_s.h   |  10 +-
 src/core/hle/service/cam/cam_u.cpp | 136 +++--
 src/core/hle/service/cam/cam_u.h   |  10 +-
 src/core/hle/service/service.cpp   |   3 +-
 11 files changed, 940 insertions(+), 792 deletions(-)

diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index 766fff1b0..8d52f2405 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -3,10 +3,6 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
-#include <array>
-#include <future>
-#include <memory>
-#include <vector>
 #include "common/bit_set.h"
 #include "common/logging/log.h"
 #include "core/core_timing.h"
@@ -14,78 +10,18 @@
 #include "core/hle/ipc.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/event.h"
-#include "core/hle/result.h"
+#include "core/hle/kernel/process.h"
 #include "core/hle/service/cam/cam.h"
 #include "core/hle/service/cam/cam_c.h"
 #include "core/hle/service/cam/cam_q.h"
 #include "core/hle/service/cam/cam_s.h"
 #include "core/hle/service/cam/cam_u.h"
-#include "core/hle/service/service.h"
 #include "core/memory.h"
 #include "core/settings.h"
 
 namespace Service {
 namespace CAM {
 
-namespace {
-
-struct ContextConfig {
-    Flip flip;
-    Effect effect;
-    OutputFormat format;
-    Resolution resolution;
-};
-
-struct CameraConfig {
-    std::unique_ptr<Camera::CameraInterface> impl;
-    std::array<ContextConfig, 2> contexts;
-    int current_context;
-    FrameRate frame_rate;
-};
-
-struct PortConfig {
-    int camera_id;
-
-    bool is_active;            // set when the port is activated by an Activate call.
-    bool is_pending_receiving; // set if SetReceiving is called when is_busy = false. When
-                               // StartCapture is called then, this will trigger a receiving
-                               // process and reset itself.
-    bool is_busy;      // set when StartCapture is called and reset when StopCapture is called.
-    bool is_receiving; // set when there is an ongoing receiving process.
-
-    bool is_trimming;
-    u16 x0; // x-coordinate of starting position for trimming
-    u16 y0; // y-coordinate of starting position for trimming
-    u16 x1; // x-coordinate of ending position for trimming
-    u16 y1; // y-coordinate of ending position for trimming
-
-    u16 transfer_bytes;
-
-    Kernel::SharedPtr<Kernel::Event> completion_event;
-    Kernel::SharedPtr<Kernel::Event> buffer_error_interrupt_event;
-    Kernel::SharedPtr<Kernel::Event> vsync_interrupt_event;
-
-    std::future<std::vector<u16>> capture_result; // will hold the received frame.
-    VAddr dest;                                   // the destination address of a receiving process
-    u32 dest_size;                                // the destination size of a receiving process
-
-    void Clear() {
-        completion_event->Clear();
-        buffer_error_interrupt_event->Clear();
-        vsync_interrupt_event->Clear();
-        is_receiving = false;
-        is_active = false;
-        is_pending_receiving = false;
-        is_busy = false;
-        is_trimming = false;
-        x0 = 0;
-        y0 = 0;
-        x1 = 0;
-        y1 = 0;
-        transfer_bytes = 256;
-    }
-};
-
 // built-in resolution parameters
 constexpr std::array<Resolution, 8> PRESET_RESOLUTION{{
     {640, 480, 0, 0, 639, 479},  // VGA
@@ -115,16 +51,28 @@ constexpr std::array<int, 13> LATENCY_BY_FRAME_RATE{{
     33,  // Rate_30_To_10
 }};
 
-std::array<CameraConfig, NumCameras> cameras;
-std::array<PortConfig, 2> ports;
-CoreTiming::EventType* completion_event_callback;
-
 const ResultCode ERROR_INVALID_ENUM_VALUE(ErrorDescription::InvalidEnumValue, ErrorModule::CAM,
                                           ErrorSummary::InvalidArgument, ErrorLevel::Usage);
 const ResultCode ERROR_OUT_OF_RANGE(ErrorDescription::OutOfRange, ErrorModule::CAM,
                                     ErrorSummary::InvalidArgument, ErrorLevel::Usage);
 
-void CompletionEventCallBack(u64 port_id, int) {
+void Module::PortConfig::Clear() {
+    completion_event->Clear();
+    buffer_error_interrupt_event->Clear();
+    vsync_interrupt_event->Clear();
+    is_receiving = false;
+    is_active = false;
+    is_pending_receiving = false;
+    is_busy = false;
+    is_trimming = false;
+    x0 = 0;
+    y0 = 0;
+    x1 = 0;
+    y1 = 0;
+    transfer_bytes = 256;
+}
+
+void Module::CompletionEventCallBack(u64 port_id, int) {
     PortConfig& port = ports[port_id];
     const CameraConfig& camera = cameras[port.camera_id];
     const auto buffer = port.capture_result.get();
@@ -165,7 +113,7 @@ void CompletionEventCallBack(u64 port_id, int) {
             if (copy_length <= 0) {
                 break;
             }
-            Memory::WriteBlock(dest_ptr, src_ptr, copy_length);
+            Memory::WriteBlock(*port.dest_process, dest_ptr, src_ptr, copy_length);
             dest_ptr += copy_length;
             dest_size_left -= copy_length;
             src_ptr += original_width;
@@ -177,16 +125,15 @@ void CompletionEventCallBack(u64 port_id, int) {
             LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%zu)!",
                       port.dest_size, buffer_size);
         }
-        Memory::WriteBlock(port.dest, buffer.data(), std::min<size_t>(port.dest_size, buffer_size));
+        Memory::WriteBlock(*port.dest_process, port.dest, buffer.data(),
+                           std::min<size_t>(port.dest_size, buffer_size));
     }
 
     port.is_receiving = false;
     port.completion_event->Signal();
 }
 
-// Starts a receiving process on the specified port. This can only be called when is_busy = true and
-// is_receiving = false.
-void StartReceiving(int port_id) {
+void Module::StartReceiving(int port_id) {
     PortConfig& port = ports[port_id];
     port.is_receiving = true;
 
@@ -202,11 +149,7 @@ void StartReceiving(int port_id) {
         completion_event_callback, port_id);
 }
 
-// Cancels any ongoing receiving processes at the specified port. This is used by functions that
-// stop capturing.
-// TODO: what is the exact behaviour on real 3DS when stopping capture during an ongoing process?
-//       Will the completion event still be signaled?
-void CancelReceiving(int port_id) {
+void Module::CancelReceiving(int port_id) {
     if (!ports[port_id].is_receiving)
         return;
     LOG_WARNING(Service_CAM, "tries to cancel an ongoing receiving process.");
@@ -215,8 +158,7 @@ void CancelReceiving(int port_id) {
     ports[port_id].is_receiving = false;
 }
 
-// Activates the specified port with the specfied camera.
-static void ActivatePort(int port_id, int camera_id) {
+void Module::ActivatePort(int port_id, int camera_id) {
     if (ports[port_id].is_busy && ports[port_id].camera_id != camera_id) {
         CancelReceiving(port_id);
         cameras[ports[port_id].camera_id].impl->StopCapture();
@@ -244,27 +186,30 @@ using PortSet = CommandParamBitSet<2>;
 using ContextSet = CommandParamBitSet<2>;
 using CameraSet = CommandParamBitSet<3>;
 
-} // namespace
+Module::Interface::Interface(std::shared_ptr<Module> cam, const char* name, u32 max_session)
+    : ServiceFramework(name, max_session), cam(std::move(cam)) {}
 
-void StartCapture(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 1, 0);
+Module::Interface::~Interface() = default;
+
+void Module::Interface::StartCapture(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x01, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 
     if (port_select.IsValid()) {
         for (int i : port_select) {
-            if (!ports[i].is_busy) {
-                if (!ports[i].is_active) {
+            if (!cam->ports[i].is_busy) {
+                if (!cam->ports[i].is_active) {
                     // This doesn't return an error, but seems to put the camera in an undefined
                     // state
                     LOG_ERROR(Service_CAM, "port %u hasn't been activated", i);
                 } else {
-                    cameras[ports[i].camera_id].impl->StartCapture();
-                    ports[i].is_busy = true;
-                    if (ports[i].is_pending_receiving) {
-                        ports[i].is_pending_receiving = false;
-                        StartReceiving(i);
+                    cam->cameras[cam->ports[i].camera_id].impl->StartCapture();
+                    cam->ports[i].is_busy = true;
+                    if (cam->ports[i].is_pending_receiving) {
+                        cam->ports[i].is_pending_receiving = false;
+                        cam->StartReceiving(i);
                     }
                 }
             } else {
@@ -280,18 +225,18 @@ void StartCapture(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
 }
 
-void StopCapture(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 1, 0);
+void Module::Interface::StopCapture(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x02, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 
     if (port_select.IsValid()) {
         for (int i : port_select) {
-            if (ports[i].is_busy) {
-                CancelReceiving(i);
-                cameras[ports[i].camera_id].impl->StopCapture();
-                ports[i].is_busy = false;
+            if (cam->ports[i].is_busy) {
+                cam->CancelReceiving(i);
+                cam->cameras[cam->ports[i].camera_id].impl->StopCapture();
+                cam->ports[i].is_busy = false;
             } else {
                 LOG_WARNING(Service_CAM, "port %u already stopped", i);
             }
@@ -305,8 +250,8 @@ void StopCapture(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
 }
 
-void IsBusy(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0);
+void Module::Interface::IsBusy(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x03, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
@@ -315,7 +260,7 @@ void IsBusy(Service::Interface* self) {
         bool is_busy = true;
         // Note: the behaviour on no or both ports selected are verified against real 3DS.
         for (int i : port_select) {
-            is_busy &= ports[i].is_busy;
+            is_busy &= cam->ports[i].is_busy;
         }
         rb.Push(RESULT_SUCCESS);
         rb.Push(is_busy);
@@ -328,8 +273,8 @@ void IsBusy(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
 }
 
-void ClearBuffer(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x04, 1, 0);
+void Module::Interface::ClearBuffer(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x04, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@@ -338,87 +283,86 @@ void ClearBuffer(Service::Interface* self) {
     LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
 }
 
-void GetVsyncInterruptEvent(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x05, 1, 0);
+void Module::Interface::GetVsyncInterruptEvent(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x05, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     if (port_select.IsSingle()) {
         int port = *port_select.begin();
         rb.Push(RESULT_SUCCESS);
-        rb.PushCopyHandles(
-            Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).Unwrap());
+        rb.PushCopyObjects(cam->ports[port].vsync_interrupt_event);
     } else {
         LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
         rb.Push(ERROR_INVALID_ENUM_VALUE);
-        rb.PushCopyHandles(0);
+        rb.PushCopyObjects<Kernel::Object>(nullptr);
     }
 
     LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
 }
 
-void GetBufferErrorInterruptEvent(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 0);
+void Module::Interface::GetBufferErrorInterruptEvent(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x06, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     if (port_select.IsSingle()) {
         int port = *port_select.begin();
         rb.Push(RESULT_SUCCESS);
-        rb.PushCopyHandles(
-            Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).Unwrap());
+        rb.PushCopyObjects(cam->ports[port].buffer_error_interrupt_event);
     } else {
         LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
         rb.Push(ERROR_INVALID_ENUM_VALUE);
-        rb.PushCopyHandles(0);
+        rb.PushCopyObjects<Kernel::Object>(nullptr);
     }
 
     LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
 }
 
-void SetReceiving(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x07, 4, 2);
+void Module::Interface::SetReceiving(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x07, 4, 2);
     const VAddr dest = rp.Pop<u32>();
     const PortSet port_select(rp.Pop<u8>());
     const u32 image_size = rp.Pop<u32>();
     const u16 trans_unit = rp.Pop<u16>();
-    rp.PopHandle(); // Handle to destination process. not used
+    auto process = rp.PopObject<Kernel::Process>();
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
     if (port_select.IsSingle()) {
         int port_id = *port_select.begin();
-        PortConfig& port = ports[port_id];
-        CancelReceiving(port_id);
+        PortConfig& port = cam->ports[port_id];
+        cam->CancelReceiving(port_id);
         port.completion_event->Clear();
+        port.dest_process = process.get();
         port.dest = dest;
         port.dest_size = image_size;
 
         if (port.is_busy) {
-            StartReceiving(port_id);
+            cam->StartReceiving(port_id);
         } else {
             port.is_pending_receiving = true;
         }
 
         rb.Push(RESULT_SUCCESS);
-        rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).Unwrap());
+        rb.PushCopyObjects(port.completion_event);
     } else {
         LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
         rb.Push(ERROR_INVALID_ENUM_VALUE);
-        rb.PushCopyHandles(0);
+        rb.PushCopyObjects<Kernel::Object>(nullptr);
     }
 
     LOG_DEBUG(Service_CAM, "called, addr=0x%X, port_select=%u, image_size=%u, trans_unit=%u", dest,
               port_select.m_val, image_size, trans_unit);
 }
 
-void IsFinishedReceiving(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x08, 1, 0);
+void Module::Interface::IsFinishedReceiving(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x08, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     if (port_select.IsSingle()) {
         int port = *port_select.begin();
-        bool is_busy = ports[port].is_receiving || ports[port].is_pending_receiving;
+        bool is_busy = cam->ports[port].is_receiving || cam->ports[port].is_pending_receiving;
         rb.Push(RESULT_SUCCESS);
         rb.Push(!is_busy);
     } else {
@@ -430,8 +374,8 @@ void IsFinishedReceiving(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
 }
 
-void SetTransferLines(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x09, 4, 0);
+void Module::Interface::SetTransferLines(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x09, 4, 0);
     const PortSet port_select(rp.Pop<u8>());
     const u16 transfer_lines = rp.Pop<u16>();
     const u16 width = rp.Pop<u16>();
@@ -440,7 +384,7 @@ void SetTransferLines(Service::Interface* self) {
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (port_select.IsValid()) {
         for (int i : port_select) {
-            ports[i].transfer_bytes = transfer_lines * width * 2;
+            cam->ports[i].transfer_bytes = transfer_lines * width * 2;
         }
         rb.Push(RESULT_SUCCESS);
     } else {
@@ -452,8 +396,8 @@ void SetTransferLines(Service::Interface* self) {
                 port_select.m_val, transfer_lines, width, height);
 }
 
-void GetMaxLines(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0A, 2, 0);
+void Module::Interface::GetMaxLines(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0A, 2, 0);
     const u16 width = rp.Pop<u16>();
     const u16 height = rp.Pop<u16>();
 
@@ -485,8 +429,8 @@ void GetMaxLines(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);
 }
 
-void SetTransferBytes(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0B, 4, 0);
+void Module::Interface::SetTransferBytes(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0B, 4, 0);
     const PortSet port_select(rp.Pop<u8>());
     const u16 transfer_bytes = rp.Pop<u16>();
     const u16 width = rp.Pop<u16>();
@@ -495,7 +439,7 @@ void SetTransferBytes(Service::Interface* self) {
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (port_select.IsValid()) {
         for (int i : port_select) {
-            ports[i].transfer_bytes = transfer_bytes;
+            cam->ports[i].transfer_bytes = transfer_bytes;
         }
         rb.Push(RESULT_SUCCESS);
     } else {
@@ -507,15 +451,15 @@ void SetTransferBytes(Service::Interface* self) {
                 port_select.m_val, transfer_bytes, width, height);
 }
 
-void GetTransferBytes(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0C, 1, 0);
+void Module::Interface::GetTransferBytes(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0C, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     if (port_select.IsSingle()) {
         int port = *port_select.begin();
         rb.Push(RESULT_SUCCESS);
-        rb.Push(ports[port].transfer_bytes);
+        rb.Push(cam->ports[port].transfer_bytes);
     } else {
         LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
         rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -525,8 +469,8 @@ void GetTransferBytes(Service::Interface* self) {
     LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u", port_select.m_val);
 }
 
-void GetMaxBytes(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0D, 2, 0);
+void Module::Interface::GetMaxBytes(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0D, 2, 0);
     const u16 width = rp.Pop<u16>();
     const u16 height = rp.Pop<u16>();
 
@@ -552,15 +496,15 @@ void GetMaxBytes(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);
 }
 
-void SetTrimming(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0E, 2, 0);
+void Module::Interface::SetTrimming(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0E, 2, 0);
     const PortSet port_select(rp.Pop<u8>());
     const bool trim = rp.Pop<bool>();
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (port_select.IsValid()) {
         for (int i : port_select) {
-            ports[i].is_trimming = trim;
+            cam->ports[i].is_trimming = trim;
         }
         rb.Push(RESULT_SUCCESS);
     } else {
@@ -571,15 +515,15 @@ void SetTrimming(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, port_select=%u, trim=%d", port_select.m_val, trim);
 }
 
-void IsTrimming(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0F, 1, 0);
+void Module::Interface::IsTrimming(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x0F, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     if (port_select.IsSingle()) {
         int port = *port_select.begin();
         rb.Push(RESULT_SUCCESS);
-        rb.Push(ports[port].is_trimming);
+        rb.Push(cam->ports[port].is_trimming);
     } else {
         LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
         rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -589,8 +533,8 @@ void IsTrimming(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
 }
 
-void SetTrimmingParams(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x10, 5, 0);
+void Module::Interface::SetTrimmingParams(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x10, 5, 0);
     const PortSet port_select(rp.Pop<u8>());
     const u16 x0 = rp.Pop<u16>();
     const u16 y0 = rp.Pop<u16>();
@@ -600,10 +544,10 @@ void SetTrimmingParams(Service::Interface* self) {
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (port_select.IsValid()) {
         for (int i : port_select) {
-            ports[i].x0 = x0;
-            ports[i].y0 = y0;
-            ports[i].x1 = x1;
-            ports[i].y1 = y1;
+            cam->ports[i].x0 = x0;
+            cam->ports[i].y0 = y0;
+            cam->ports[i].x1 = x1;
+            cam->ports[i].y1 = y1;
         }
         rb.Push(RESULT_SUCCESS);
     } else {
@@ -615,18 +559,18 @@ void SetTrimmingParams(Service::Interface* self) {
               x0, y0, x1, y1);
 }
 
-void GetTrimmingParams(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x11, 1, 0);
+void Module::Interface::GetTrimmingParams(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x11, 1, 0);
     const PortSet port_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(5, 0);
     if (port_select.IsSingle()) {
         int port = *port_select.begin();
         rb.Push(RESULT_SUCCESS);
-        rb.Push(ports[port].x0);
-        rb.Push(ports[port].y0);
-        rb.Push(ports[port].x1);
-        rb.Push(ports[port].y1);
+        rb.Push(cam->ports[port].x0);
+        rb.Push(cam->ports[port].y0);
+        rb.Push(cam->ports[port].x1);
+        rb.Push(cam->ports[port].y1);
     } else {
         LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
         rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -636,8 +580,8 @@ void GetTrimmingParams(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
 }
 
-void SetTrimmingParamsCenter(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x12, 5, 0);
+void Module::Interface::SetTrimmingParamsCenter(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x12, 5, 0);
     const PortSet port_select(rp.Pop<u8>());
     const u16 trim_w = rp.Pop<u16>();
     const u16 trim_h = rp.Pop<u16>();
@@ -647,10 +591,10 @@ void SetTrimmingParamsCenter(Service::Interface* self) {
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (port_select.IsValid()) {
         for (int i : port_select) {
-            ports[i].x0 = (cam_w - trim_w) / 2;
-            ports[i].y0 = (cam_h - trim_h) / 2;
-            ports[i].x1 = ports[i].x0 + trim_w;
-            ports[i].y1 = ports[i].y0 + trim_h;
+            cam->ports[i].x0 = (cam_w - trim_w) / 2;
+            cam->ports[i].y0 = (cam_h - trim_h) / 2;
+            cam->ports[i].x1 = cam->ports[i].x0 + trim_w;
+            cam->ports[i].y1 = cam->ports[i].y0 + trim_h;
         }
         rb.Push(RESULT_SUCCESS);
     } else {
@@ -662,20 +606,20 @@ void SetTrimmingParamsCenter(Service::Interface* self) {
               port_select.m_val, trim_w, trim_h, cam_w, cam_h);
 }
 
-void Activate(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x13, 1, 0);
+void Module::Interface::Activate(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x13, 1, 0);
     const CameraSet camera_select(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (camera_select.IsValid()) {
         if (camera_select.m_val == 0) { // deactive all
             for (int i = 0; i < 2; ++i) {
-                if (ports[i].is_busy) {
-                    CancelReceiving(i);
-                    cameras[ports[i].camera_id].impl->StopCapture();
-                    ports[i].is_busy = false;
+                if (cam->ports[i].is_busy) {
+                    cam->CancelReceiving(i);
+                    cam->cameras[cam->ports[i].camera_id].impl->StopCapture();
+                    cam->ports[i].is_busy = false;
                 }
-                ports[i].is_active = false;
+                cam->ports[i].is_active = false;
             }
             rb.Push(RESULT_SUCCESS);
         } else if (camera_select[0] && camera_select[1]) {
@@ -683,13 +627,13 @@ void Activate(Service::Interface* self) {
             rb.Push(ERROR_INVALID_ENUM_VALUE);
         } else {
             if (camera_select[0]) {
-                ActivatePort(0, 0);
+                cam->ActivatePort(0, 0);
             } else if (camera_select[1]) {
-                ActivatePort(0, 1);
+                cam->ActivatePort(0, 1);
             }
 
             if (camera_select[2]) {
-                ActivatePort(1, 2);
+                cam->ActivatePort(1, 2);
             }
             rb.Push(RESULT_SUCCESS);
         }
@@ -701,8 +645,8 @@ void Activate(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called, camera_select=%u", camera_select.m_val);
 }
 
-void SwitchContext(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x14, 2, 0);
+void Module::Interface::SwitchContext(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x14, 2, 0);
     const CameraSet camera_select(rp.Pop<u8>());
     const ContextSet context_select(rp.Pop<u8>());
 
@@ -710,12 +654,12 @@ void SwitchContext(Service::Interface* self) {
     if (camera_select.IsValid() && context_select.IsSingle()) {
         int context = *context_select.begin();
         for (int camera : camera_select) {
-            cameras[camera].current_context = context;
-            const ContextConfig& context_config = cameras[camera].contexts[context];
-            cameras[camera].impl->SetFlip(context_config.flip);
-            cameras[camera].impl->SetEffect(context_config.effect);
-            cameras[camera].impl->SetFormat(context_config.format);
-            cameras[camera].impl->SetResolution(context_config.resolution);
+            cam->cameras[camera].current_context = context;
+            const ContextConfig& context_config = cam->cameras[camera].contexts[context];
+            cam->cameras[camera].impl->SetFlip(context_config.flip);
+            cam->cameras[camera].impl->SetEffect(context_config.effect);
+            cam->cameras[camera].impl->SetFormat(context_config.format);
+            cam->cameras[camera].impl->SetResolution(context_config.resolution);
         }
         rb.Push(RESULT_SUCCESS);
     } else {
@@ -728,8 +672,8 @@ void SwitchContext(Service::Interface* self) {
               context_select.m_val);
 }
 
-void FlipImage(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1D, 3, 0);
+void Module::Interface::FlipImage(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1D, 3, 0);
     const CameraSet camera_select(rp.Pop<u8>());
     const Flip flip = static_cast<Flip>(rp.Pop<u8>());
     const ContextSet context_select(rp.Pop<u8>());
@@ -738,9 +682,9 @@ void FlipImage(Service::Interface* self) {
     if (camera_select.IsValid() && context_select.IsValid()) {
         for (int camera : camera_select) {
             for (int context : context_select) {
-                cameras[camera].contexts[context].flip = flip;
-                if (cameras[camera].current_context == context) {
-                    cameras[camera].impl->SetFlip(flip);
+                cam->cameras[camera].contexts[context].flip = flip;
+                if (cam->cameras[camera].current_context == context) {
+                    cam->cameras[camera].impl->SetFlip(flip);
                 }
             }
         }
@@ -755,8 +699,8 @@ void FlipImage(Service::Interface* self) {
               camera_select.m_val, static_cast<int>(flip), context_select.m_val);
 }
 
-void SetDetailSize(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 8, 0);
+void Module::Interface::SetDetailSize(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1E, 8, 0);
     const CameraSet camera_select(rp.Pop<u8>());
     Resolution resolution;
     resolution.width = rp.Pop<u16>();
@@ -771,9 +715,9 @@ void SetDetailSize(Service::Interface* self) {
     if (camera_select.IsValid() && context_select.IsValid()) {
         for (int camera : camera_select) {
             for (int context : context_select) {
-                cameras[camera].contexts[context].resolution = resolution;
-                if (cameras[camera].current_context == context) {
-                    cameras[camera].impl->SetResolution(resolution);
+                cam->cameras[camera].contexts[context].resolution = resolution;
+                if (cam->cameras[camera].current_context == context) {
+                    cam->cameras[camera].impl->SetResolution(resolution);
                 }
             }
         }
@@ -790,8 +734,8 @@ void SetDetailSize(Service::Interface* self) {
               resolution.crop_y0, resolution.crop_x1, resolution.crop_y1, context_select.m_val);
 }
 
-void SetSize(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1F, 3, 0);
+void Module::Interface::SetSize(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x1F, 3, 0);
     const CameraSet camera_select(rp.Pop<u8>());
     const u8 size = rp.Pop<u8>();
     const ContextSet context_select(rp.Pop<u8>());
@@ -800,9 +744,9 @@ void SetSize(Service::Interface* self) {
     if (camera_select.IsValid() && context_select.IsValid()) {
         for (int camera : camera_select) {
             for (int context : context_select) {
-                cameras[camera].contexts[context].resolution = PRESET_RESOLUTION[size];
-                if (cameras[camera].current_context == context) {
-                    cameras[camera].impl->SetResolution(PRESET_RESOLUTION[size]);
+                cam->cameras[camera].contexts[context].resolution = PRESET_RESOLUTION[size];
+                if (cam->cameras[camera].current_context == context) {
+                    cam->cameras[camera].impl->SetResolution(PRESET_RESOLUTION[size]);
                 }
             }
         }
@@ -817,15 +761,15 @@ void SetSize(Service::Interface* self) {
               camera_select.m_val, size, context_select.m_val);
 }
 
-void SetFrameRate(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x20, 2, 0);
+void Module::Interface::SetFrameRate(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x20, 2, 0);
     const CameraSet camera_select(rp.Pop<u8>());
     const FrameRate frame_rate = static_cast<FrameRate>(rp.Pop<u8>());
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (camera_select.IsValid()) {
         for (int camera : camera_select) {
-            cameras[camera].frame_rate = frame_rate;
+            cam->cameras[camera].frame_rate = frame_rate;
             // TODO(wwylele): consider hinting the actual camera with the expected frame rate
         }
         rb.Push(RESULT_SUCCESS);
@@ -838,8 +782,8 @@ void SetFrameRate(Service::Interface* self) {
                 camera_select.m_val, static_cast<int>(frame_rate));
 }
 
-void SetEffect(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x22, 3, 0);
+void Module::Interface::SetEffect(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x22, 3, 0);
     const CameraSet camera_select(rp.Pop<u8>());
     const Effect effect = static_cast<Effect>(rp.Pop<u8>());
     const ContextSet context_select(rp.Pop<u8>());
@@ -848,9 +792,9 @@ void SetEffect(Service::Interface* self) {
     if (camera_select.IsValid() && context_select.IsValid()) {
         for (int camera : camera_select) {
             for (int context : context_select) {
-                cameras[camera].contexts[context].effect = effect;
-                if (cameras[camera].current_context == context) {
-                    cameras[camera].impl->SetEffect(effect);
+                cam->cameras[camera].contexts[context].effect = effect;
+                if (cam->cameras[camera].current_context == context) {
+                    cam->cameras[camera].impl->SetEffect(effect);
                 }
             }
         }
@@ -865,8 +809,8 @@ void SetEffect(Service::Interface* self) {
               camera_select.m_val, static_cast<int>(effect), context_select.m_val);
 }
 
-void SetOutputFormat(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x25, 3, 0);
+void Module::Interface::SetOutputFormat(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x25, 3, 0);
     const CameraSet camera_select(rp.Pop<u8>());
     const OutputFormat format = static_cast<OutputFormat>(rp.Pop<u8>());
     const ContextSet context_select(rp.Pop<u8>());
@@ -875,9 +819,9 @@ void SetOutputFormat(Service::Interface* self) {
     if (camera_select.IsValid() && context_select.IsValid()) {
         for (int camera : camera_select) {
             for (int context : context_select) {
-                cameras[camera].contexts[context].format = format;
-                if (cameras[camera].current_context == context) {
-                    cameras[camera].impl->SetFormat(format);
+                cam->cameras[camera].contexts[context].format = format;
+                if (cam->cameras[camera].current_context == context) {
+                    cam->cameras[camera].impl->SetFormat(format);
                 }
             }
         }
@@ -892,8 +836,8 @@ void SetOutputFormat(Service::Interface* self) {
               camera_select.m_val, static_cast<int>(format), context_select.m_val);
 }
 
-void SynchronizeVsyncTiming(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x29, 2, 0);
+void Module::Interface::SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x29, 2, 0);
     const u8 camera_select1 = rp.Pop<u8>();
     const u8 camera_select2 = rp.Pop<u8>();
 
@@ -904,9 +848,8 @@ void SynchronizeVsyncTiming(Service::Interface* self) {
                 camera_select1, camera_select2);
 }
 
-void GetStereoCameraCalibrationData(Service::Interface* self) {
-    IPC::RequestBuilder rb =
-        IPC::RequestParser(Kernel::GetCommandBuffer(), 0x2B, 0, 0).MakeBuilder(17, 0);
+void Module::Interface::GetStereoCameraCalibrationData(Kernel::HLERequestContext& ctx) {
+    IPC::RequestBuilder rb = IPC::RequestParser(ctx, 0x2B, 0, 0).MakeBuilder(17, 0);
 
     // Default values taken from yuriks' 3DS. Valid data is required here or games using the
     // calibration get stuck in an infinite CPU loop.
@@ -931,8 +874,8 @@ void GetStereoCameraCalibrationData(Service::Interface* self) {
     LOG_TRACE(Service_CAM, "called");
 }
 
-void SetPackageParameterWithoutContext(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x33, 11, 0);
+void Module::Interface::SetPackageParameterWithoutContext(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x33, 11, 0);
 
     PackageParameterWithoutContext package;
     rp.PopRaw(package);
@@ -944,7 +887,7 @@ void SetPackageParameterWithoutContext(Service::Interface* self) {
 }
 
 template <typename PackageParameterType>
-static ResultCode SetPackageParameter(const PackageParameterType& package) {
+ResultCode Module::SetPackageParameter(const PackageParameterType& package) {
     const CameraSet camera_select(package.camera_select);
     const ContextSet context_select(package.context_select);
 
@@ -975,34 +918,34 @@ Resolution PackageParameterWithContext::GetResolution() const {
     return PRESET_RESOLUTION[static_cast<int>(size)];
 }
 
-void SetPackageParameterWithContext(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x34, 5, 0);
+void Module::Interface::SetPackageParameterWithContext(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x34, 5, 0);
 
     PackageParameterWithContext package;
     rp.PopRaw(package);
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    ResultCode result = SetPackageParameter(package);
+    ResultCode result = cam->SetPackageParameter(package);
     rb.Push(result);
 
     LOG_DEBUG(Service_CAM, "called");
 }
 
-void SetPackageParameterWithContextDetail(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x35, 7, 0);
+void Module::Interface::SetPackageParameterWithContextDetail(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x35, 7, 0);
 
     PackageParameterWithContextDetail package;
     rp.PopRaw(package);
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
-    ResultCode result = SetPackageParameter(package);
+    ResultCode result = cam->SetPackageParameter(package);
     rb.Push(result);
 
     LOG_DEBUG(Service_CAM, "called");
 }
 
-void GetSuitableY2rStandardCoefficient(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x36, 0, 0);
+void Module::Interface::GetSuitableY2rStandardCoefficient(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x36, 0, 0);
     IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
     rb.Push(RESULT_SUCCESS);
     rb.Push<u32>(0);
@@ -1010,8 +953,8 @@ void GetSuitableY2rStandardCoefficient(Service::Interface* self) {
     LOG_WARNING(Service_CAM, "(STUBBED) called");
 }
 
-void PlayShutterSound(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x38, 1, 0);
+void Module::Interface::PlayShutterSound(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x38, 1, 0);
     u8 sound_id = rp.Pop<u8>();
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@@ -1020,12 +963,12 @@ void PlayShutterSound(Service::Interface* self) {
     LOG_WARNING(Service_CAM, "(STUBBED) called, sound_id=%d", sound_id);
 }
 
-void DriverInitialize(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x39, 0, 0);
+void Module::Interface::DriverInitialize(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x39, 0, 0);
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 
     for (int camera_id = 0; camera_id < NumCameras; ++camera_id) {
-        CameraConfig& camera = cameras[camera_id];
+        CameraConfig& camera = cam->cameras[camera_id];
         camera.current_context = 0;
         for (int context_id = 0; context_id < 2; ++context_id) {
             // Note: the following default values are verified against real 3DS
@@ -1044,7 +987,7 @@ void DriverInitialize(Service::Interface* self) {
         camera.impl->SetResolution(camera.contexts[0].resolution);
     }
 
-    for (PortConfig& port : ports) {
+    for (PortConfig& port : cam->ports) {
         port.Clear();
     }
 
@@ -1053,14 +996,14 @@ void DriverInitialize(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called");
 }
 
-void DriverFinalize(Service::Interface* self) {
-    IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3A, 0, 0);
+void Module::Interface::DriverFinalize(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp(ctx, 0x3A, 0, 0);
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
 
-    CancelReceiving(0);
-    CancelReceiving(1);
+    cam->CancelReceiving(0);
+    cam->CancelReceiving(1);
 
-    for (CameraConfig& camera : cameras) {
+    for (CameraConfig& camera : cam->cameras) {
         camera.impl = nullptr;
     }
 
@@ -1069,36 +1012,31 @@ void DriverFinalize(Service::Interface* self) {
     LOG_DEBUG(Service_CAM, "called");
 }
 
-void Init() {
+Module::Module() {
     using namespace Kernel;
-
-    AddService(new CAM_C_Interface);
-    AddService(new CAM_Q_Interface);
-    AddService(new CAM_S_Interface);
-    AddService(new CAM_U_Interface);
-
     for (PortConfig& port : ports) {
-        port.completion_event = Event::Create(ResetType::Sticky, "CAM_U::completion_event");
+        port.completion_event = Event::Create(ResetType::Sticky, "CAM::completion_event");
         port.buffer_error_interrupt_event =
-            Event::Create(ResetType::OneShot, "CAM_U::buffer_error_interrupt_event");
+            Event::Create(ResetType::OneShot, "CAM::buffer_error_interrupt_event");
         port.vsync_interrupt_event =
-            Event::Create(ResetType::OneShot, "CAM_U::vsync_interrupt_event");
+            Event::Create(ResetType::OneShot, "CAM::vsync_interrupt_event");
     }
-    completion_event_callback =
-        CoreTiming::RegisterEvent("CAM_U::CompletionEventCallBack", CompletionEventCallBack);
+    completion_event_callback = CoreTiming::RegisterEvent(
+        "CAM::CompletionEventCallBack",
+        [this](u64 userdata, int cycles_late) { CompletionEventCallBack(userdata, cycles_late); });
 }
 
-void Shutdown() {
+Module::~Module() {
     CancelReceiving(0);
     CancelReceiving(1);
-    for (PortConfig& port : ports) {
-        port.completion_event = nullptr;
-        port.buffer_error_interrupt_event = nullptr;
-        port.vsync_interrupt_event = nullptr;
-    }
-    for (CameraConfig& camera : cameras) {
-        camera.impl = nullptr;
-    }
+}
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+    auto cam = std::make_shared<Module>();
+    std::make_shared<CAM_U>(cam)->InstallAsService(service_manager);
+    std::make_shared<CAM_S>(cam)->InstallAsService(service_manager);
+    std::make_shared<CAM_C>(cam)->InstallAsService(service_manager);
+    std::make_shared<CAM_Q>()->InstallAsService(service_manager);
 }
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h
index b6da721d8..c1c13eae6 100644
--- a/src/core/hle/service/cam/cam.h
+++ b/src/core/hle/service/cam/cam.h
@@ -4,12 +4,27 @@
 
 #pragma once
 
-#include "common/common_funcs.h"
+#include <array>
+#include <future>
+#include <memory>
+#include <vector>
 #include "common/common_types.h"
 #include "common/swap.h"
-#include "core/hle/kernel/kernel.h"
+#include "core/hle/result.h"
 #include "core/hle/service/service.h"
 
+namespace Camera {
+class CameraInterface;
+}
+
+namespace CoreTiming {
+class EventType;
+}
+
+namespace Kernel {
+class Process;
+}
+
 namespace Service {
 namespace CAM {
 
@@ -221,466 +236,547 @@ struct PackageParameterWithContextDetail {
 static_assert(sizeof(PackageParameterWithContextDetail) == 28,
               "PackageParameterWithContextDetail structure size is wrong");
 
-/**
- * Starts capturing at the selected port.
- *  Inputs:
- *      0: 0x00010040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x00010040
- *      1: ResultCode
- */
-void StartCapture(Service::Interface* self);
+class Module final {
+public:
+    Module();
+    ~Module();
 
-/**
- * Stops capturing from the selected port.
- *  Inputs:
- *      0: 0x00020040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x00020040
- *      1: ResultCode
- */
-void StopCapture(Service::Interface* self);
+    class Interface : public ServiceFramework<Interface> {
+    public:
+        Interface(std::shared_ptr<Module> cam, const char* name, u32 max_session);
+        ~Interface();
 
-/**
- * Gets whether the selected port is currently capturing.
- *  Inputs:
- *      0: 0x00030040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x00030080
- *      1: ResultCode
- *      2: 0 if not capturing, 1 if capturing
- */
-void IsBusy(Service::Interface* self);
+    protected:
+        /**
+         * Starts capturing at the selected port.
+         *  Inputs:
+         *      0: 0x00010040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x00010040
+         *      1: ResultCode
+         */
+        void StartCapture(Kernel::HLERequestContext& ctx);
 
-/**
- * Clears the buffer of selected ports.
- *  Inputs:
- *      0: 0x00040040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x00040040
- *      2: ResultCode
- */
-void ClearBuffer(Service::Interface* self);
+        /**
+         * Stops capturing from the selected port.
+         *  Inputs:
+         *      0: 0x00020040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x00020040
+         *      1: ResultCode
+         */
+        void StopCapture(Kernel::HLERequestContext& ctx);
 
-/**
- * Unknown
- *  Inputs:
- *      0: 0x00050040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x00050042
- *      1: ResultCode
- *      2: Descriptor: Handle
- *      3: Event handle
- */
-void GetVsyncInterruptEvent(Service::Interface* self);
+        /**
+         * Gets whether the selected port is currently capturing.
+         *  Inputs:
+         *      0: 0x00030040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x00030080
+         *      1: ResultCode
+         *      2: 0 if not capturing, 1 if capturing
+         */
+        void IsBusy(Kernel::HLERequestContext& ctx);
 
-/**
- * Unknown
- *  Inputs:
- *      0: 0x00060040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x00060042
- *      1: ResultCode
- *      2: Descriptor: Handle
- *      3: Event handle
- */
-void GetBufferErrorInterruptEvent(Service::Interface* self);
+        /**
+         * Clears the buffer of selected ports.
+         *  Inputs:
+         *      0: 0x00040040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x00040040
+         *      2: ResultCode
+         */
+        void ClearBuffer(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets the target buffer to receive a frame of image data and starts the transfer. Each camera
- * port has its own event to signal the end of the transfer.
- *
- *  Inputs:
- *      0: 0x00070102
- *      1: Destination address in calling process
- *      2: u8 selected port
- *      3: Image size (in bytes)
- *      4: u16 Transfer unit size (in bytes)
- *      5: Descriptor: Handle
- *      6: Handle to destination process
- *  Outputs:
- *      0: 0x00070042
- *      1: ResultCode
- *      2: Descriptor: Handle
- *      3: Handle to event signalled when transfer finishes
- */
-void SetReceiving(Service::Interface* self);
+        /**
+         * Unknown
+         *  Inputs:
+         *      0: 0x00050040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x00050042
+         *      1: ResultCode
+         *      2: Descriptor: Handle
+         *      3: Event handle
+         */
+        void GetVsyncInterruptEvent(Kernel::HLERequestContext& ctx);
 
-/**
- * Gets whether the selected port finished receiving a frame.
- *  Inputs:
- *      0: 0x00080040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x00080080
- *      1: ResultCode
- *      2: 0 if not finished, 1 if finished
- */
-void IsFinishedReceiving(Service::Interface* self);
+        /**
+         * Unknown
+         *  Inputs:
+         *      0: 0x00060040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x00060042
+         *      1: ResultCode
+         *      2: Descriptor: Handle
+         *      3: Event handle
+         */
+        void GetBufferErrorInterruptEvent(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets the number of lines the buffer contains.
- *  Inputs:
- *      0: 0x00090100
- *      1: u8 selected port
- *      2: u16 Number of lines to transfer
- *      3: u16 Width
- *      4: u16 Height
- *  Outputs:
- *      0: 0x00090040
- *      1: ResultCode
- * @todo figure out how the "buffer" actually works.
- */
-void SetTransferLines(Service::Interface* self);
+        /**
+         * Sets the target buffer to receive a frame of image data and starts the transfer. Each
+         * camera port has its own event to signal the end of the transfer.
+         *
+         *  Inputs:
+         *      0: 0x00070102
+         *      1: Destination address in calling process
+         *      2: u8 selected port
+         *      3: Image size (in bytes)
+         *      4: u16 Transfer unit size (in bytes)
+         *      5: Descriptor: Handle
+         *      6: Handle to destination process
+         *  Outputs:
+         *      0: 0x00070042
+         *      1: ResultCode
+         *      2: Descriptor: Handle
+         *      3: Handle to event signalled when transfer finishes
+         */
+        void SetReceiving(Kernel::HLERequestContext& ctx);
 
-/**
- * Gets the maximum number of lines that fit in the buffer
- *  Inputs:
- *      0: 0x000A0080
- *      1: u16 Width
- *      2: u16 Height
- *  Outputs:
- *      0: 0x000A0080
- *      1: ResultCode
- *      2: Maximum number of lines that fit in the buffer
- * @todo figure out how the "buffer" actually works.
- */
-void GetMaxLines(Service::Interface* self);
+        /**
+         * Gets whether the selected port finished receiving a frame.
+         *  Inputs:
+         *      0: 0x00080040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x00080080
+         *      1: ResultCode
+         *      2: 0 if not finished, 1 if finished
+         */
+        void IsFinishedReceiving(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets the number of bytes the buffer contains.
- *  Inputs:
- *      0: 0x000B0100
- *      1: u8 selected port
- *      2: u16 Number of bytes to transfer
- *      3: u16 Width
- *      4: u16 Height
- *  Outputs:
- *      0: 0x000B0040
- *      1: ResultCode
- * @todo figure out how the "buffer" actually works.
- */
-void SetTransferBytes(Service::Interface* self);
+        /**
+         * Sets the number of lines the buffer contains.
+         *  Inputs:
+         *      0: 0x00090100
+         *      1: u8 selected port
+         *      2: u16 Number of lines to transfer
+         *      3: u16 Width
+         *      4: u16 Height
+         *  Outputs:
+         *      0: 0x00090040
+         *      1: ResultCode
+         * @todo figure out how the "buffer" actually works.
+         */
+        void SetTransferLines(Kernel::HLERequestContext& ctx);
 
-/**
- * Gets the number of bytes to the buffer contains.
- *  Inputs:
- *      0: 0x000C0040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x000C0080
- *      1: ResultCode
- *      2: The number of bytes the buffer contains
- * @todo figure out how the "buffer" actually works.
- */
-void GetTransferBytes(Service::Interface* self);
+        /**
+         * Gets the maximum number of lines that fit in the buffer
+         *  Inputs:
+         *      0: 0x000A0080
+         *      1: u16 Width
+         *      2: u16 Height
+         *  Outputs:
+         *      0: 0x000A0080
+         *      1: ResultCode
+         *      2: Maximum number of lines that fit in the buffer
+         * @todo figure out how the "buffer" actually works.
+         */
+        void GetMaxLines(Kernel::HLERequestContext& ctx);
 
-/**
- * Gets the maximum number of bytes that fit in the buffer.
- *  Inputs:
- *      0: 0x000D0080
- *      1: u16 Width
- *      2: u16 Height
- *  Outputs:
- *      0: 0x000D0080
- *      1: ResultCode
- *      2: Maximum number of bytes that fit in the buffer
- * @todo figure out how the "buffer" actually works.
- */
-void GetMaxBytes(Service::Interface* self);
+        /**
+         * Sets the number of bytes the buffer contains.
+         *  Inputs:
+         *      0: 0x000B0100
+         *      1: u8 selected port
+         *      2: u16 Number of bytes to transfer
+         *      3: u16 Width
+         *      4: u16 Height
+         *  Outputs:
+         *      0: 0x000B0040
+         *      1: ResultCode
+         * @todo figure out how the "buffer" actually works.
+         */
+        void SetTransferBytes(Kernel::HLERequestContext& ctx);
 
-/**
- * Enables or disables trimming.
- *  Inputs:
- *      0: 0x000E0080
- *      1: u8 selected port
- *      2: u8 bool Enable trimming if true
- *  Outputs:
- *      0: 0x000E0040
- *      1: ResultCode
- */
-void SetTrimming(Service::Interface* self);
+        /**
+         * Gets the number of bytes to the buffer contains.
+         *  Inputs:
+         *      0: 0x000C0040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x000C0080
+         *      1: ResultCode
+         *      2: The number of bytes the buffer contains
+         * @todo figure out how the "buffer" actually works.
+         */
+        void GetTransferBytes(Kernel::HLERequestContext& ctx);
 
-/**
- * Gets whether trimming is enabled.
- *  Inputs:
- *      0: 0x000F0040
- *      1: u8 selected port
- *  Outputs:
- *      0: 0x000F0080
- *      1: ResultCode
- *      2: u8 bool Enable trimming if true
- */
-void IsTrimming(Service::Interface* self);
+        /**
+         * Gets the maximum number of bytes that fit in the buffer.
+         *  Inputs:
+         *      0: 0x000D0080
+         *      1: u16 Width
+         *      2: u16 Height
+         *  Outputs:
+         *      0: 0x000D0080
+         *      1: ResultCode
+         *      2: Maximum number of bytes that fit in the buffer
+         * @todo figure out how the "buffer" actually works.
+         */
+        void GetMaxBytes(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets the position to trim.
- *  Inputs:
- *      0: 0x00100140
- *      1: u8 selected port
- *      2: x start
- *      3: y start
- *      4: x end (exclusive)
- *      5: y end (exclusive)
- *  Outputs:
- *      0: 0x00100040
- *      1: ResultCode
- */
-void SetTrimmingParams(Service::Interface* self);
+        /**
+         * Enables or disables trimming.
+         *  Inputs:
+         *      0: 0x000E0080
+         *      1: u8 selected port
+         *      2: u8 bool Enable trimming if true
+         *  Outputs:
+         *      0: 0x000E0040
+         *      1: ResultCode
+         */
+        void SetTrimming(Kernel::HLERequestContext& ctx);
 
-/**
- * Gets the position to trim.
- *  Inputs:
- *      0: 0x00110040
- *      1: u8 selected port
- *
- *  Outputs:
- *      0: 0x00110140
- *      1: ResultCode
- *      2: x start
- *      3: y start
- *      4: x end (exclusive)
- *      5: y end (exclusive)
- */
-void GetTrimmingParams(Service::Interface* self);
+        /**
+         * Gets whether trimming is enabled.
+         *  Inputs:
+         *      0: 0x000F0040
+         *      1: u8 selected port
+         *  Outputs:
+         *      0: 0x000F0080
+         *      1: ResultCode
+         *      2: u8 bool Enable trimming if true
+         */
+        void IsTrimming(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets the position to trim by giving the width and height. The trimming window is always at the
- * center.
- *  Inputs:
- *      0: 0x00120140
- *      1: u8 selected port
- *      2: s16 Trim width
- *      3: s16 Trim height
- *      4: s16 Camera width
- *      5: s16 Camera height
- *  Outputs:
- *      0: 0x00120040
- *      1: ResultCode
- */
-void SetTrimmingParamsCenter(Service::Interface* self);
+        /**
+         * Sets the position to trim.
+         *  Inputs:
+         *      0: 0x00100140
+         *      1: u8 selected port
+         *      2: x start
+         *      3: y start
+         *      4: x end (exclusive)
+         *      5: y end (exclusive)
+         *  Outputs:
+         *      0: 0x00100040
+         *      1: ResultCode
+         */
+        void SetTrimmingParams(Kernel::HLERequestContext& ctx);
 
-/**
- * Selects up to two physical cameras to enable.
- *  Inputs:
- *      0: 0x00130040
- *      1: u8 selected camera
- *  Outputs:
- *      0: 0x00130040
- *      1: ResultCode
- */
-void Activate(Service::Interface* self);
+        /**
+         * Gets the position to trim.
+         *  Inputs:
+         *      0: 0x00110040
+         *      1: u8 selected port
+         *
+         *  Outputs:
+         *      0: 0x00110140
+         *      1: ResultCode
+         *      2: x start
+         *      3: y start
+         *      4: x end (exclusive)
+         *      5: y end (exclusive)
+         */
+        void GetTrimmingParams(Kernel::HLERequestContext& ctx);
 
-/**
- * Switches the context of camera settings.
- *  Inputs:
- *      0: 0x00140080
- *      1: u8 selected camera
- *      2: u8 selected context
- *  Outputs:
- *      0: 0x00140040
- *      1: ResultCode
- */
-void SwitchContext(Service::Interface* self);
+        /**
+         * Sets the position to trim by giving the width and height. The trimming window is always
+         * at the center.
+         *  Inputs:
+         *      0: 0x00120140
+         *      1: u8 selected port
+         *      2: s16 Trim width
+         *      3: s16 Trim height
+         *      4: s16 Camera width
+         *      5: s16 Camera height
+         *  Outputs:
+         *      0: 0x00120040
+         *      1: ResultCode
+         */
+        void SetTrimmingParamsCenter(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets flipping of images
- *  Inputs:
- *      0: 0x001D00C0
- *      1: u8 selected camera
- *      2: u8 Type of flipping to perform (`Flip` enum)
- *      3: u8 selected context
- *  Outputs:
- *      0: 0x001D0040
- *      1: ResultCode
- */
-void FlipImage(Service::Interface* self);
+        /**
+         * Selects up to two physical cameras to enable.
+         *  Inputs:
+         *      0: 0x00130040
+         *      1: u8 selected camera
+         *  Outputs:
+         *      0: 0x00130040
+         *      1: ResultCode
+         */
+        void Activate(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets camera resolution from custom parameters. For more details see the Resolution struct.
- *  Inputs:
- *      0: 0x001E0200
- *      1: u8 selected camera
- *      2: width
- *      3: height
- *      4: crop x0
- *      5: crop y0
- *      6: crop x1
- *      7: crop y1
- *      8: u8 selected context
- *  Outputs:
- *      0: 0x001E0040
- *      1: ResultCode
- */
-void SetDetailSize(Service::Interface* self);
+        /**
+         * Switches the context of camera settings.
+         *  Inputs:
+         *      0: 0x00140080
+         *      1: u8 selected camera
+         *      2: u8 selected context
+         *  Outputs:
+         *      0: 0x00140040
+         *      1: ResultCode
+         */
+        void SwitchContext(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets camera resolution from preset resolution parameters.
- *  Inputs:
- *      0: 0x001F00C0
- *      1: u8 selected camera
- *      2: u8 Camera frame resolution (`Size` enum)
- *      3: u8 selected context
- *  Outputs:
- *      0: 0x001F0040
- *      1: ResultCode
- */
-void SetSize(Service::Interface* self);
+        /**
+         * Sets flipping of images
+         *  Inputs:
+         *      0: 0x001D00C0
+         *      1: u8 selected camera
+         *      2: u8 Type of flipping to perform (`Flip` enum)
+         *      3: u8 selected context
+         *  Outputs:
+         *      0: 0x001D0040
+         *      1: ResultCode
+         */
+        void FlipImage(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets camera framerate.
- *  Inputs:
- *      0: 0x00200080
- *      1: u8 selected camera
- *      2: u8 Camera framerate (`FrameRate` enum)
- *  Outputs:
- *      0: 0x00200040
- *      1: ResultCode
- */
-void SetFrameRate(Service::Interface* self);
+        /**
+         * Sets camera resolution from custom parameters. For more details see the Resolution
+         * struct.
+         *  Inputs:
+         *      0: 0x001E0200
+         *      1: u8 selected camera
+         *      2: width
+         *      3: height
+         *      4: crop x0
+         *      5: crop y0
+         *      6: crop x1
+         *      7: crop y1
+         *      8: u8 selected context
+         *  Outputs:
+         *      0: 0x001E0040
+         *      1: ResultCode
+         */
+        void SetDetailSize(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets effect on the output image
- *  Inputs:
- *      0: 0x002200C0
- *      1: u8 selected camera
- *      2: u8 image effect (`Effect` enum)
- *      3: u8 selected context
- *  Outputs:
- *      0: 0x00220040
- *      1: ResultCode
- */
-void SetEffect(Service::Interface* self);
+        /**
+         * Sets camera resolution from preset resolution parameters.
+         *  Inputs:
+         *      0: 0x001F00C0
+         *      1: u8 selected camera
+         *      2: u8 Camera frame resolution (`Size` enum)
+         *      3: u8 selected context
+         *  Outputs:
+         *      0: 0x001F0040
+         *      1: ResultCode
+         */
+        void SetSize(Kernel::HLERequestContext& ctx);
 
-/**
- * Sets format of the output image
- *  Inputs:
- *      0: 0x002500C0
- *      1: u8 selected camera
- *      2: u8 image format (`OutputFormat` enum)
- *      3: u8 selected context
- *  Outputs:
- *      0: 0x00250040
- *      1: ResultCode
- */
-void SetOutputFormat(Service::Interface* self);
+        /**
+         * Sets camera framerate.
+         *  Inputs:
+         *      0: 0x00200080
+         *      1: u8 selected camera
+         *      2: u8 Camera framerate (`FrameRate` enum)
+         *  Outputs:
+         *      0: 0x00200040
+         *      1: ResultCode
+         */
+        void SetFrameRate(Kernel::HLERequestContext& ctx);
 
-/**
- * Synchronizes the V-Sync timing of two cameras.
- *  Inputs:
- *      0: 0x00290080
- *      1: u8 selected camera 1
- *      2: u8 selected camera 2
- *  Outputs:
- *      0: 0x00280040
- *      1: ResultCode
- */
-void SynchronizeVsyncTiming(Service::Interface* self);
+        /**
+         * Sets effect on the output image
+         *  Inputs:
+         *      0: 0x002200C0
+         *      1: u8 selected camera
+         *      2: u8 image effect (`Effect` enum)
+         *      3: u8 selected context
+         *  Outputs:
+         *      0: 0x00220040
+         *      1: ResultCode
+         */
+        void SetEffect(Kernel::HLERequestContext& ctx);
 
-/**
- * Returns calibration data relating the outside cameras to eachother, for use in AR applications.
- *
- *  Inputs:
- *      0: 0x002B0000
- *  Outputs:
- *      0: 0x002B0440
- *      1: ResultCode
- *      2-17: `StereoCameraCalibrationData` structure with calibration values
- */
-void GetStereoCameraCalibrationData(Service::Interface* self);
+        /**
+         * Sets format of the output image
+         *  Inputs:
+         *      0: 0x002500C0
+         *      1: u8 selected camera
+         *      2: u8 image format (`OutputFormat` enum)
+         *      3: u8 selected context
+         *  Outputs:
+         *      0: 0x00250040
+         *      1: ResultCode
+         */
+        void SetOutputFormat(Kernel::HLERequestContext& ctx);
 
-/**
- * Batch-configures context-free settings.
- *
- *  Inputs:
- *      0: 0x003302C0
- *      1-7: struct PachageParameterWithoutContext
- *      8-11: unused
- *  Outputs:
- *      0: 0x00330040
- *      1: ResultCode
- */
-void SetPackageParameterWithoutContext(Service::Interface* self);
+        /**
+         * Synchronizes the V-Sync timing of two cameras.
+         *  Inputs:
+         *      0: 0x00290080
+         *      1: u8 selected camera 1
+         *      2: u8 selected camera 2
+         *  Outputs:
+         *      0: 0x00280040
+         *      1: ResultCode
+         */
+        void SynchronizeVsyncTiming(Kernel::HLERequestContext& ctx);
 
-/**
- * Batch-configures context-related settings with preset resolution parameters.
- *
- *  Inputs:
- *      0: 0x00340140
- *      1-2: struct PackageParameterWithContext
- *      3-5: unused
- *  Outputs:
- *      0: 0x00340040
- *      1: ResultCode
- */
-void SetPackageParameterWithContext(Service::Interface* self);
+        /**
+         * Returns calibration data relating the outside cameras to each other, for use in AR
+         * applications.
+         *
+         *  Inputs:
+         *      0: 0x002B0000
+         *  Outputs:
+         *      0: 0x002B0440
+         *      1: ResultCode
+         *      2-17: `StereoCameraCalibrationData` structure with calibration values
+         */
+        void GetStereoCameraCalibrationData(Kernel::HLERequestContext& ctx);
 
-/**
- * Batch-configures context-related settings with custom resolution parameters
- *
- *  Inputs:
- *      0: 0x003501C0
- *      1-4: struct PackageParameterWithContextDetail
- *      5-7: unused
- *  Outputs:
- *      0: 0x00350040
- *      1: ResultCode
- */
-void SetPackageParameterWithContextDetail(Service::Interface* self);
+        /**
+         * Batch-configures context-free settings.
+         *
+         *  Inputs:
+         *      0: 0x003302C0
+         *      1-7: struct PachageParameterWithoutContext
+         *      8-11: unused
+         *  Outputs:
+         *      0: 0x00330040
+         *      1: ResultCode
+         */
+        void SetPackageParameterWithoutContext(Kernel::HLERequestContext& ctx);
 
-/**
- * Unknown
- *  Inputs:
- *      0: 0x00360000
- *  Outputs:
- *      0: 0x00360080
- *      1: ResultCode
- *      2: ?
- */
-void GetSuitableY2rStandardCoefficient(Service::Interface* self);
+        /**
+         * Batch-configures context-related settings with preset resolution parameters.
+         *
+         *  Inputs:
+         *      0: 0x00340140
+         *      1-2: struct PackageParameterWithContext
+         *      3-5: unused
+         *  Outputs:
+         *      0: 0x00340040
+         *      1: ResultCode
+         */
+        void SetPackageParameterWithContext(Kernel::HLERequestContext& ctx);
 
-/**
- * Unknown
- *  Inputs:
- *      0: 0x00380040
- *      1: u8 Sound ID
- *  Outputs:
- *      0: 0x00380040
- *      1: ResultCode
- */
-void PlayShutterSound(Service::Interface* self);
+        /**
+         * Batch-configures context-related settings with custom resolution parameters
+         *
+         *  Inputs:
+         *      0: 0x003501C0
+         *      1-4: struct PackageParameterWithContextDetail
+         *      5-7: unused
+         *  Outputs:
+         *      0: 0x00350040
+         *      1: ResultCode
+         */
+        void SetPackageParameterWithContextDetail(Kernel::HLERequestContext& ctx);
 
-/**
- * Initializes the camera driver. Must be called before using other functions.
- *  Inputs:
- *      0: 0x00390000
- *  Outputs:
- *      0: 0x00390040
- *      1: ResultCode
- */
-void DriverInitialize(Service::Interface* self);
+        /**
+         * Unknown
+         *  Inputs:
+         *      0: 0x00360000
+         *  Outputs:
+         *      0: 0x00360080
+         *      1: ResultCode
+         *      2: ?
+         */
+        void GetSuitableY2rStandardCoefficient(Kernel::HLERequestContext& ctx);
 
-/**
- * Shuts down the camera driver.
- *  Inputs:
- *      0: 0x003A0000
- *  Outputs:
- *      0: 0x003A0040
- *      1: ResultCode
- */
-void DriverFinalize(Service::Interface* self);
+        /**
+         * Unknown
+         *  Inputs:
+         *      0: 0x00380040
+         *      1: u8 Sound ID
+         *  Outputs:
+         *      0: 0x00380040
+         *      1: ResultCode
+         */
+        void PlayShutterSound(Kernel::HLERequestContext& ctx);
 
-/// Initialize CAM service(s)
-void Init();
+        /**
+         * Initializes the camera driver. Must be called before using other functions.
+         *  Inputs:
+         *      0: 0x00390000
+         *  Outputs:
+         *      0: 0x00390040
+         *      1: ResultCode
+         */
+        void DriverInitialize(Kernel::HLERequestContext& ctx);
 
-/// Shutdown CAM service(s)
-void Shutdown();
+        /**
+         * Shuts down the camera driver.
+         *  Inputs:
+         *      0: 0x003A0000
+         *  Outputs:
+         *      0: 0x003A0040
+         *      1: ResultCode
+         */
+        void DriverFinalize(Kernel::HLERequestContext& ctx);
+
+    private:
+        std::shared_ptr<Module> cam;
+    };
+
+private:
+    void CompletionEventCallBack(u64 port_id, int);
+
+    // Starts a receiving process on the specified port. This can only be called when is_busy = true
+    // and is_receiving = false.
+    void StartReceiving(int port_id);
+
+    // Cancels any ongoing receiving processes at the specified port. This is used by functions that
+    // stop capturing.
+    // TODO: what is the exact behaviour on real 3DS when stopping capture during an ongoing
+    //       process? Will the completion event still be signaled?
+    void CancelReceiving(int port_id);
+
+    // Activates the specified port with the specfied camera.
+    void ActivatePort(int port_id, int camera_id);
+
+    template <typename PackageParameterType>
+    ResultCode SetPackageParameter(const PackageParameterType& package);
+
+    struct ContextConfig {
+        Flip flip;
+        Effect effect;
+        OutputFormat format;
+        Resolution resolution;
+    };
+
+    struct CameraConfig {
+        std::unique_ptr<Camera::CameraInterface> impl;
+        std::array<ContextConfig, 2> contexts;
+        int current_context;
+        FrameRate frame_rate;
+    };
+
+    struct PortConfig {
+        int camera_id;
+
+        bool is_active;            // set when the port is activated by an Activate call.
+        bool is_pending_receiving; // set if SetReceiving is called when is_busy = false. When
+        // StartCapture is called then, this will trigger a receiving
+        // process and reset itself.
+        bool is_busy;      // set when StartCapture is called and reset when StopCapture is called.
+        bool is_receiving; // set when there is an ongoing receiving process.
+
+        bool is_trimming;
+        u16 x0; // x-coordinate of starting position for trimming
+        u16 y0; // y-coordinate of starting position for trimming
+        u16 x1; // x-coordinate of ending position for trimming
+        u16 y1; // y-coordinate of ending position for trimming
+
+        u16 transfer_bytes;
+
+        Kernel::SharedPtr<Kernel::Event> completion_event;
+        Kernel::SharedPtr<Kernel::Event> buffer_error_interrupt_event;
+        Kernel::SharedPtr<Kernel::Event> vsync_interrupt_event;
+
+        std::future<std::vector<u16>> capture_result; // will hold the received frame.
+        Kernel::Process* dest_process;
+        VAddr dest;    // the destination address of the receiving process
+        u32 dest_size; // the destination size of the receiving process
+
+        void Clear();
+    };
+
+    std::array<CameraConfig, NumCameras> cameras;
+    std::array<PortConfig, 2> ports;
+    CoreTiming::EventType* completion_event_callback;
+};
+
+void InstallInterfaces(SM::ServiceManager& service_manager);
 
 } // namespace CAM
 } // namespace Service
diff --git a/src/core/hle/service/cam/cam_c.cpp b/src/core/hle/service/cam/cam_c.cpp
index 93b047c1a..db46413cc 100644
--- a/src/core/hle/service/cam/cam_c.cpp
+++ b/src/core/hle/service/cam/cam_c.cpp
@@ -2,16 +2,81 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "core/hle/service/cam/cam.h"
 #include "core/hle/service/cam/cam_c.h"
 
 namespace Service {
 namespace CAM {
 
-// Empty arrays are illegal -- commented out until an entry is added.
-// const Interface::FunctionInfo FunctionTable[] = { };
-
-CAM_C_Interface::CAM_C_Interface() {
-    // Register(FunctionTable);
+CAM_C::CAM_C(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "cam:c", 1) {
+    static const FunctionInfo functions[] = {
+        {0x00010040, &CAM_C::StartCapture, "StartCapture"},
+        {0x00020040, &CAM_C::StopCapture, "StopCapture"},
+        {0x00030040, &CAM_C::IsBusy, "IsBusy"},
+        {0x00040040, &CAM_C::ClearBuffer, "ClearBuffer"},
+        {0x00050040, &CAM_C::GetVsyncInterruptEvent, "GetVsyncInterruptEvent"},
+        {0x00060040, &CAM_C::GetBufferErrorInterruptEvent, "GetBufferErrorInterruptEvent"},
+        {0x00070102, &CAM_C::SetReceiving, "SetReceiving"},
+        {0x00080040, &CAM_C::IsFinishedReceiving, "IsFinishedReceiving"},
+        {0x00090100, &CAM_C::SetTransferLines, "SetTransferLines"},
+        {0x000A0080, &CAM_C::GetMaxLines, "GetMaxLines"},
+        {0x000B0100, &CAM_C::SetTransferBytes, "SetTransferBytes"},
+        {0x000C0040, &CAM_C::GetTransferBytes, "GetTransferBytes"},
+        {0x000D0080, &CAM_C::GetMaxBytes, "GetMaxBytes"},
+        {0x000E0080, &CAM_C::SetTrimming, "SetTrimming"},
+        {0x000F0040, &CAM_C::IsTrimming, "IsTrimming"},
+        {0x00100140, &CAM_C::SetTrimmingParams, "SetTrimmingParams"},
+        {0x00110040, &CAM_C::GetTrimmingParams, "GetTrimmingParams"},
+        {0x00120140, &CAM_C::SetTrimmingParamsCenter, "SetTrimmingParamsCenter"},
+        {0x00130040, &CAM_C::Activate, "Activate"},
+        {0x00140080, &CAM_C::SwitchContext, "SwitchContext"},
+        {0x00150080, nullptr, "SetExposure"},
+        {0x00160080, nullptr, "SetWhiteBalance"},
+        {0x00170080, nullptr, "SetWhiteBalanceWithoutBaseUp"},
+        {0x00180080, nullptr, "SetSharpness"},
+        {0x00190080, nullptr, "SetAutoExposure"},
+        {0x001A0040, nullptr, "IsAutoExposure"},
+        {0x001B0080, nullptr, "SetAutoWhiteBalance"},
+        {0x001C0040, nullptr, "IsAutoWhiteBalance"},
+        {0x001D00C0, &CAM_C::FlipImage, "FlipImage"},
+        {0x001E0200, &CAM_C::SetDetailSize, "SetDetailSize"},
+        {0x001F00C0, &CAM_C::SetSize, "SetSize"},
+        {0x00200080, &CAM_C::SetFrameRate, "SetFrameRate"},
+        {0x00210080, nullptr, "SetPhotoMode"},
+        {0x002200C0, &CAM_C::SetEffect, "SetEffect"},
+        {0x00230080, nullptr, "SetContrast"},
+        {0x00240080, nullptr, "SetLensCorrection"},
+        {0x002500C0, &CAM_C::SetOutputFormat, "SetOutputFormat"},
+        {0x00260140, nullptr, "SetAutoExposureWindow"},
+        {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
+        {0x00280080, nullptr, "SetNoiseFilter"},
+        {0x00290080, &CAM_C::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
+        {0x002A0080, nullptr, "GetLatestVsyncTiming"},
+        {0x002B0000, &CAM_C::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
+        {0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
+        {0x002D00C0, nullptr, "WriteRegisterI2c"},
+        {0x002E00C0, nullptr, "WriteMcuVariableI2c"},
+        {0x002F0080, nullptr, "ReadRegisterI2cExclusive"},
+        {0x00300080, nullptr, "ReadMcuVariableI2cExclusive"},
+        {0x00310180, nullptr, "SetImageQualityCalibrationData"},
+        {0x00320000, nullptr, "GetImageQualityCalibrationData"},
+        {0x003302C0, &CAM_C::SetPackageParameterWithoutContext,
+         "SetPackageParameterWithoutContext"},
+        {0x00340140, &CAM_C::SetPackageParameterWithContext, "SetPackageParameterWithContext"},
+        {0x003501C0, &CAM_C::SetPackageParameterWithContextDetail,
+         "SetPackageParameterWithContextDetail"},
+        {0x00360000, &CAM_C::GetSuitableY2rStandardCoefficient,
+         "GetSuitableY2rStandardCoefficient"},
+        {0x00370202, nullptr, "PlayShutterSoundWithWave"},
+        {0x00380040, &CAM_C::PlayShutterSound, "PlayShutterSound"},
+        {0x00390000, &CAM_C::DriverInitialize, "DriverInitialize"},
+        {0x003A0000, &CAM_C::DriverFinalize, "DriverFinalize"},
+        {0x003B0000, nullptr, "GetActivatedCamera"},
+        {0x003C0000, nullptr, "GetSleepCamera"},
+        {0x003D0040, nullptr, "SetSleepCamera"},
+        {0x003E0040, nullptr, "SetBrightnessSynchronization"},
+    };
+    RegisterHandlers(functions);
 }
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam_c.h b/src/core/hle/service/cam/cam_c.h
index 6b296c00d..e68887c57 100644
--- a/src/core/hle/service/cam/cam_c.h
+++ b/src/core/hle/service/cam/cam_c.h
@@ -4,18 +4,14 @@
 
 #pragma once
 
-#include "core/hle/service/service.h"
+#include "core/hle/service/cam/cam.h"
 
 namespace Service {
 namespace CAM {
 
-class CAM_C_Interface : public Service::Interface {
+class CAM_C final : public Module::Interface {
 public:
-    CAM_C_Interface();
-
-    std::string GetPortName() const override {
-        return "cam:c";
-    }
+    explicit CAM_C(std::shared_ptr<Module> cam);
 };
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam_q.cpp b/src/core/hle/service/cam/cam_q.cpp
index 2ba853606..ed4067f4c 100644
--- a/src/core/hle/service/cam/cam_q.cpp
+++ b/src/core/hle/service/cam/cam_q.cpp
@@ -7,11 +7,10 @@
 namespace Service {
 namespace CAM {
 
-// Empty arrays are illegal -- commented out until an entry is added.
-// const Interface::FunctionInfo FunctionTable[] = { };
-
-CAM_Q_Interface::CAM_Q_Interface() {
-    // Register(FunctionTable);
+CAM_Q::CAM_Q() : ServiceFramework("cam:q", 1 /*TODO: find the true value*/) {
+    // Empty arrays are illegal -- commented out until an entry is added.
+    // static const FunctionInfo functions[] = {};
+    // RegisterHandlers(functions);
 }
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam_q.h b/src/core/hle/service/cam/cam_q.h
index 07cc12534..a96698668 100644
--- a/src/core/hle/service/cam/cam_q.h
+++ b/src/core/hle/service/cam/cam_q.h
@@ -9,13 +9,9 @@
 namespace Service {
 namespace CAM {
 
-class CAM_Q_Interface : public Service::Interface {
+class CAM_Q : public ServiceFramework<CAM_Q> {
 public:
-    CAM_Q_Interface();
-
-    std::string GetPortName() const override {
-        return "cam:q";
-    }
+    CAM_Q();
 };
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam_s.cpp b/src/core/hle/service/cam/cam_s.cpp
index f1c6da587..5e8bd1cd9 100644
--- a/src/core/hle/service/cam/cam_s.cpp
+++ b/src/core/hle/service/cam/cam_s.cpp
@@ -2,16 +2,81 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "core/hle/service/cam/cam.h"
 #include "core/hle/service/cam/cam_s.h"
 
 namespace Service {
 namespace CAM {
 
-// Empty arrays are illegal -- commented out until an entry is added.
-// const Interface::FunctionInfo FunctionTable[] = { };
-
-CAM_S_Interface::CAM_S_Interface() {
-    // Register(FunctionTable);
+CAM_S::CAM_S(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "cam:s", 1) {
+    static const FunctionInfo functions[] = {
+        {0x00010040, &CAM_S::StartCapture, "StartCapture"},
+        {0x00020040, &CAM_S::StopCapture, "StopCapture"},
+        {0x00030040, &CAM_S::IsBusy, "IsBusy"},
+        {0x00040040, &CAM_S::ClearBuffer, "ClearBuffer"},
+        {0x00050040, &CAM_S::GetVsyncInterruptEvent, "GetVsyncInterruptEvent"},
+        {0x00060040, &CAM_S::GetBufferErrorInterruptEvent, "GetBufferErrorInterruptEvent"},
+        {0x00070102, &CAM_S::SetReceiving, "SetReceiving"},
+        {0x00080040, &CAM_S::IsFinishedReceiving, "IsFinishedReceiving"},
+        {0x00090100, &CAM_S::SetTransferLines, "SetTransferLines"},
+        {0x000A0080, &CAM_S::GetMaxLines, "GetMaxLines"},
+        {0x000B0100, &CAM_S::SetTransferBytes, "SetTransferBytes"},
+        {0x000C0040, &CAM_S::GetTransferBytes, "GetTransferBytes"},
+        {0x000D0080, &CAM_S::GetMaxBytes, "GetMaxBytes"},
+        {0x000E0080, &CAM_S::SetTrimming, "SetTrimming"},
+        {0x000F0040, &CAM_S::IsTrimming, "IsTrimming"},
+        {0x00100140, &CAM_S::SetTrimmingParams, "SetTrimmingParams"},
+        {0x00110040, &CAM_S::GetTrimmingParams, "GetTrimmingParams"},
+        {0x00120140, &CAM_S::SetTrimmingParamsCenter, "SetTrimmingParamsCenter"},
+        {0x00130040, &CAM_S::Activate, "Activate"},
+        {0x00140080, &CAM_S::SwitchContext, "SwitchContext"},
+        {0x00150080, nullptr, "SetExposure"},
+        {0x00160080, nullptr, "SetWhiteBalance"},
+        {0x00170080, nullptr, "SetWhiteBalanceWithoutBaseUp"},
+        {0x00180080, nullptr, "SetSharpness"},
+        {0x00190080, nullptr, "SetAutoExposure"},
+        {0x001A0040, nullptr, "IsAutoExposure"},
+        {0x001B0080, nullptr, "SetAutoWhiteBalance"},
+        {0x001C0040, nullptr, "IsAutoWhiteBalance"},
+        {0x001D00C0, &CAM_S::FlipImage, "FlipImage"},
+        {0x001E0200, &CAM_S::SetDetailSize, "SetDetailSize"},
+        {0x001F00C0, &CAM_S::SetSize, "SetSize"},
+        {0x00200080, &CAM_S::SetFrameRate, "SetFrameRate"},
+        {0x00210080, nullptr, "SetPhotoMode"},
+        {0x002200C0, &CAM_S::SetEffect, "SetEffect"},
+        {0x00230080, nullptr, "SetContrast"},
+        {0x00240080, nullptr, "SetLensCorrection"},
+        {0x002500C0, &CAM_S::SetOutputFormat, "SetOutputFormat"},
+        {0x00260140, nullptr, "SetAutoExposureWindow"},
+        {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
+        {0x00280080, nullptr, "SetNoiseFilter"},
+        {0x00290080, &CAM_S::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
+        {0x002A0080, nullptr, "GetLatestVsyncTiming"},
+        {0x002B0000, &CAM_S::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
+        {0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
+        {0x002D00C0, nullptr, "WriteRegisterI2c"},
+        {0x002E00C0, nullptr, "WriteMcuVariableI2c"},
+        {0x002F0080, nullptr, "ReadRegisterI2cExclusive"},
+        {0x00300080, nullptr, "ReadMcuVariableI2cExclusive"},
+        {0x00310180, nullptr, "SetImageQualityCalibrationData"},
+        {0x00320000, nullptr, "GetImageQualityCalibrationData"},
+        {0x003302C0, &CAM_S::SetPackageParameterWithoutContext,
+         "SetPackageParameterWithoutContext"},
+        {0x00340140, &CAM_S::SetPackageParameterWithContext, "SetPackageParameterWithContext"},
+        {0x003501C0, &CAM_S::SetPackageParameterWithContextDetail,
+         "SetPackageParameterWithContextDetail"},
+        {0x00360000, &CAM_S::GetSuitableY2rStandardCoefficient,
+         "GetSuitableY2rStandardCoefficient"},
+        {0x00370202, nullptr, "PlayShutterSoundWithWave"},
+        {0x00380040, &CAM_S::PlayShutterSound, "PlayShutterSound"},
+        {0x00390000, &CAM_S::DriverInitialize, "DriverInitialize"},
+        {0x003A0000, &CAM_S::DriverFinalize, "DriverFinalize"},
+        {0x003B0000, nullptr, "GetActivatedCamera"},
+        {0x003C0000, nullptr, "GetSleepCamera"},
+        {0x003D0040, nullptr, "SetSleepCamera"},
+        {0x003E0040, nullptr, "SetBrightnessSynchronization"},
+    };
+    RegisterHandlers(functions);
 }
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam_s.h b/src/core/hle/service/cam/cam_s.h
index 0a5d6fca2..f958b4261 100644
--- a/src/core/hle/service/cam/cam_s.h
+++ b/src/core/hle/service/cam/cam_s.h
@@ -4,18 +4,14 @@
 
 #pragma once
 
-#include "core/hle/service/service.h"
+#include "core/hle/service/cam/cam.h"
 
 namespace Service {
 namespace CAM {
 
-class CAM_S_Interface : public Service::Interface {
+class CAM_S final : public Module::Interface {
 public:
-    CAM_S_Interface();
-
-    std::string GetPortName() const override {
-        return "cam:s";
-    }
+    explicit CAM_S(std::shared_ptr<Module> cam);
 };
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp
index 251c1e6d4..f4e23ec30 100644
--- a/src/core/hle/service/cam/cam_u.cpp
+++ b/src/core/hle/service/cam/cam_u.cpp
@@ -8,73 +8,75 @@
 namespace Service {
 namespace CAM {
 
-const Interface::FunctionInfo FunctionTable[] = {
-    {0x00010040, StartCapture, "StartCapture"},
-    {0x00020040, StopCapture, "StopCapture"},
-    {0x00030040, IsBusy, "IsBusy"},
-    {0x00040040, ClearBuffer, "ClearBuffer"},
-    {0x00050040, GetVsyncInterruptEvent, "GetVsyncInterruptEvent"},
-    {0x00060040, GetBufferErrorInterruptEvent, "GetBufferErrorInterruptEvent"},
-    {0x00070102, SetReceiving, "SetReceiving"},
-    {0x00080040, IsFinishedReceiving, "IsFinishedReceiving"},
-    {0x00090100, SetTransferLines, "SetTransferLines"},
-    {0x000A0080, GetMaxLines, "GetMaxLines"},
-    {0x000B0100, SetTransferBytes, "SetTransferBytes"},
-    {0x000C0040, GetTransferBytes, "GetTransferBytes"},
-    {0x000D0080, GetMaxBytes, "GetMaxBytes"},
-    {0x000E0080, SetTrimming, "SetTrimming"},
-    {0x000F0040, IsTrimming, "IsTrimming"},
-    {0x00100140, SetTrimmingParams, "SetTrimmingParams"},
-    {0x00110040, GetTrimmingParams, "GetTrimmingParams"},
-    {0x00120140, SetTrimmingParamsCenter, "SetTrimmingParamsCenter"},
-    {0x00130040, Activate, "Activate"},
-    {0x00140080, SwitchContext, "SwitchContext"},
-    {0x00150080, nullptr, "SetExposure"},
-    {0x00160080, nullptr, "SetWhiteBalance"},
-    {0x00170080, nullptr, "SetWhiteBalanceWithoutBaseUp"},
-    {0x00180080, nullptr, "SetSharpness"},
-    {0x00190080, nullptr, "SetAutoExposure"},
-    {0x001A0040, nullptr, "IsAutoExposure"},
-    {0x001B0080, nullptr, "SetAutoWhiteBalance"},
-    {0x001C0040, nullptr, "IsAutoWhiteBalance"},
-    {0x001D00C0, FlipImage, "FlipImage"},
-    {0x001E0200, SetDetailSize, "SetDetailSize"},
-    {0x001F00C0, SetSize, "SetSize"},
-    {0x00200080, SetFrameRate, "SetFrameRate"},
-    {0x00210080, nullptr, "SetPhotoMode"},
-    {0x002200C0, SetEffect, "SetEffect"},
-    {0x00230080, nullptr, "SetContrast"},
-    {0x00240080, nullptr, "SetLensCorrection"},
-    {0x002500C0, SetOutputFormat, "SetOutputFormat"},
-    {0x00260140, nullptr, "SetAutoExposureWindow"},
-    {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
-    {0x00280080, nullptr, "SetNoiseFilter"},
-    {0x00290080, SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
-    {0x002A0080, nullptr, "GetLatestVsyncTiming"},
-    {0x002B0000, GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
-    {0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
-    {0x002D00C0, nullptr, "WriteRegisterI2c"},
-    {0x002E00C0, nullptr, "WriteMcuVariableI2c"},
-    {0x002F0080, nullptr, "ReadRegisterI2cExclusive"},
-    {0x00300080, nullptr, "ReadMcuVariableI2cExclusive"},
-    {0x00310180, nullptr, "SetImageQualityCalibrationData"},
-    {0x00320000, nullptr, "GetImageQualityCalibrationData"},
-    {0x003302C0, SetPackageParameterWithoutContext, "SetPackageParameterWithoutContext"},
-    {0x00340140, SetPackageParameterWithContext, "SetPackageParameterWithContext"},
-    {0x003501C0, SetPackageParameterWithContextDetail, "SetPackageParameterWithContextDetail"},
-    {0x00360000, GetSuitableY2rStandardCoefficient, "GetSuitableY2rStandardCoefficient"},
-    {0x00370202, nullptr, "PlayShutterSoundWithWave"},
-    {0x00380040, PlayShutterSound, "PlayShutterSound"},
-    {0x00390000, DriverInitialize, "DriverInitialize"},
-    {0x003A0000, DriverFinalize, "DriverFinalize"},
-    {0x003B0000, nullptr, "GetActivatedCamera"},
-    {0x003C0000, nullptr, "GetSleepCamera"},
-    {0x003D0040, nullptr, "SetSleepCamera"},
-    {0x003E0040, nullptr, "SetBrightnessSynchronization"},
-};
-
-CAM_U_Interface::CAM_U_Interface() {
-    Register(FunctionTable);
+CAM_U::CAM_U(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "cam:u", 1) {
+    static const FunctionInfo functions[] = {
+        {0x00010040, &CAM_U::StartCapture, "StartCapture"},
+        {0x00020040, &CAM_U::StopCapture, "StopCapture"},
+        {0x00030040, &CAM_U::IsBusy, "IsBusy"},
+        {0x00040040, &CAM_U::ClearBuffer, "ClearBuffer"},
+        {0x00050040, &CAM_U::GetVsyncInterruptEvent, "GetVsyncInterruptEvent"},
+        {0x00060040, &CAM_U::GetBufferErrorInterruptEvent, "GetBufferErrorInterruptEvent"},
+        {0x00070102, &CAM_U::SetReceiving, "SetReceiving"},
+        {0x00080040, &CAM_U::IsFinishedReceiving, "IsFinishedReceiving"},
+        {0x00090100, &CAM_U::SetTransferLines, "SetTransferLines"},
+        {0x000A0080, &CAM_U::GetMaxLines, "GetMaxLines"},
+        {0x000B0100, &CAM_U::SetTransferBytes, "SetTransferBytes"},
+        {0x000C0040, &CAM_U::GetTransferBytes, "GetTransferBytes"},
+        {0x000D0080, &CAM_U::GetMaxBytes, "GetMaxBytes"},
+        {0x000E0080, &CAM_U::SetTrimming, "SetTrimming"},
+        {0x000F0040, &CAM_U::IsTrimming, "IsTrimming"},
+        {0x00100140, &CAM_U::SetTrimmingParams, "SetTrimmingParams"},
+        {0x00110040, &CAM_U::GetTrimmingParams, "GetTrimmingParams"},
+        {0x00120140, &CAM_U::SetTrimmingParamsCenter, "SetTrimmingParamsCenter"},
+        {0x00130040, &CAM_U::Activate, "Activate"},
+        {0x00140080, &CAM_U::SwitchContext, "SwitchContext"},
+        {0x00150080, nullptr, "SetExposure"},
+        {0x00160080, nullptr, "SetWhiteBalance"},
+        {0x00170080, nullptr, "SetWhiteBalanceWithoutBaseUp"},
+        {0x00180080, nullptr, "SetSharpness"},
+        {0x00190080, nullptr, "SetAutoExposure"},
+        {0x001A0040, nullptr, "IsAutoExposure"},
+        {0x001B0080, nullptr, "SetAutoWhiteBalance"},
+        {0x001C0040, nullptr, "IsAutoWhiteBalance"},
+        {0x001D00C0, &CAM_U::FlipImage, "FlipImage"},
+        {0x001E0200, &CAM_U::SetDetailSize, "SetDetailSize"},
+        {0x001F00C0, &CAM_U::SetSize, "SetSize"},
+        {0x00200080, &CAM_U::SetFrameRate, "SetFrameRate"},
+        {0x00210080, nullptr, "SetPhotoMode"},
+        {0x002200C0, &CAM_U::SetEffect, "SetEffect"},
+        {0x00230080, nullptr, "SetContrast"},
+        {0x00240080, nullptr, "SetLensCorrection"},
+        {0x002500C0, &CAM_U::SetOutputFormat, "SetOutputFormat"},
+        {0x00260140, nullptr, "SetAutoExposureWindow"},
+        {0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
+        {0x00280080, nullptr, "SetNoiseFilter"},
+        {0x00290080, &CAM_U::SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
+        {0x002A0080, nullptr, "GetLatestVsyncTiming"},
+        {0x002B0000, &CAM_U::GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
+        {0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
+        {0x002D00C0, nullptr, "WriteRegisterI2c"},
+        {0x002E00C0, nullptr, "WriteMcuVariableI2c"},
+        {0x002F0080, nullptr, "ReadRegisterI2cExclusive"},
+        {0x00300080, nullptr, "ReadMcuVariableI2cExclusive"},
+        {0x00310180, nullptr, "SetImageQualityCalibrationData"},
+        {0x00320000, nullptr, "GetImageQualityCalibrationData"},
+        {0x003302C0, &CAM_U::SetPackageParameterWithoutContext,
+         "SetPackageParameterWithoutContext"},
+        {0x00340140, &CAM_U::SetPackageParameterWithContext, "SetPackageParameterWithContext"},
+        {0x003501C0, &CAM_U::SetPackageParameterWithContextDetail,
+         "SetPackageParameterWithContextDetail"},
+        {0x00360000, &CAM_U::GetSuitableY2rStandardCoefficient,
+         "GetSuitableY2rStandardCoefficient"},
+        {0x00370202, nullptr, "PlayShutterSoundWithWave"},
+        {0x00380040, &CAM_U::PlayShutterSound, "PlayShutterSound"},
+        {0x00390000, &CAM_U::DriverInitialize, "DriverInitialize"},
+        {0x003A0000, &CAM_U::DriverFinalize, "DriverFinalize"},
+        {0x003B0000, nullptr, "GetActivatedCamera"},
+        {0x003C0000, nullptr, "GetSleepCamera"},
+        {0x003D0040, nullptr, "SetSleepCamera"},
+        {0x003E0040, nullptr, "SetBrightnessSynchronization"},
+    };
+    RegisterHandlers(functions);
 }
 
 } // namespace CAM
diff --git a/src/core/hle/service/cam/cam_u.h b/src/core/hle/service/cam/cam_u.h
index 369264037..a76daf0e3 100644
--- a/src/core/hle/service/cam/cam_u.h
+++ b/src/core/hle/service/cam/cam_u.h
@@ -4,18 +4,14 @@
 
 #pragma once
 
-#include "core/hle/service/service.h"
+#include "core/hle/service/cam/cam.h"
 
 namespace Service {
 namespace CAM {
 
-class CAM_U_Interface : public Service::Interface {
+class CAM_U final : public Module::Interface {
 public:
-    CAM_U_Interface();
-
-    std::string GetPortName() const override {
-        return "cam:u";
-    }
+    explicit CAM_U(std::shared_ptr<Module> cam);
 };
 
 } // namespace CAM
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 039c86a07..d96518dc4 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -267,7 +267,7 @@ void Init() {
     AM::Init();
     APT::Init();
     BOSS::Init();
-    CAM::Init();
+    CAM::InstallInterfaces(*SM::g_service_manager);
     CECD::Init();
     CFG::Init();
     DLP::Init();
@@ -311,7 +311,6 @@ void Shutdown() {
     DLP::Shutdown();
     CFG::Shutdown();
     CECD::Shutdown();
-    CAM::Shutdown();
     BOSS::Shutdown();
     APT::Shutdown();
     AM::Shutdown();