diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index cdb3bf6ab..d65659b44 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -209,8 +209,6 @@ add_library(core STATIC
     hle/service/apm/apm.h
     hle/service/apm/interface.cpp
     hle/service/apm/interface.h
-    hle/service/arp/arp.cpp
-    hle/service/arp/arp.h
     hle/service/audio/audctl.cpp
     hle/service/audio/audctl.h
     hle/service/audio/auddbg.cpp
@@ -276,6 +274,15 @@ add_library(core STATIC
     hle/service/friend/friend.h
     hle/service/friend/interface.cpp
     hle/service/friend/interface.h
+    hle/service/glue/arp.cpp
+    hle/service/glue/arp.h
+    hle/service/glue/bgtc.cpp
+    hle/service/glue/bgtc.h
+    hle/service/glue/errors.h
+    hle/service/glue/glue.cpp
+    hle/service/glue/glue.h
+    hle/service/glue/manager.cpp
+    hle/service/glue/manager.h
     hle/service/grc/grc.cpp
     hle/service/grc/grc.h
     hle/service/hid/hid.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index c00dfd33c..df26eb109 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -25,6 +25,7 @@
 #include "core/hle/kernel/scheduler.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/service/am/applets/applets.h"
+#include "core/hle/service/glue/manager.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/sm/sm.h"
 #include "core/loader/loader.h"
@@ -33,12 +34,37 @@
 #include "core/settings.h"
 #include "core/telemetry_session.h"
 #include "file_sys/cheat_engine.h"
+#include "file_sys/patch_manager.h"
 #include "video_core/debug_utils/debug_utils.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 
 namespace Core {
 
+namespace {
+
+FileSys::StorageId GetStorageIdForFrontendSlot(
+    std::optional<FileSys::ContentProviderUnionSlot> slot) {
+    if (!slot.has_value()) {
+        return FileSys::StorageId::None;
+    }
+
+    switch (*slot) {
+    case FileSys::ContentProviderUnionSlot::UserNAND:
+        return FileSys::StorageId::NandUser;
+    case FileSys::ContentProviderUnionSlot::SysNAND:
+        return FileSys::StorageId::NandSystem;
+    case FileSys::ContentProviderUnionSlot::SDMC:
+        return FileSys::StorageId::SdCard;
+    case FileSys::ContentProviderUnionSlot::FrontendManual:
+        return FileSys::StorageId::Host;
+    default:
+        return FileSys::StorageId::None;
+    }
+}
+
+} // Anonymous namespace
+
 /*static*/ System System::s_instance;
 
 FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
@@ -110,6 +136,9 @@ struct System::Impl {
         /// Create default implementations of applets if one is not provided.
         applet_manager.SetDefaultAppletsIfMissing();
 
+        /// Reset all glue registrations
+        arp_manager.ResetAll();
+
         telemetry_session = std::make_unique<Core::TelemetrySession>();
         service_manager = std::make_shared<Service::SM::ServiceManager>();
 
@@ -161,6 +190,7 @@ struct System::Impl {
             return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
                                              static_cast<u32>(load_result));
         }
+        AddGlueRegistrationForProcess(*app_loader, *main_process);
         kernel.MakeCurrentProcess(main_process.get());
 
         // Main process has been loaded and been made current.
@@ -219,6 +249,31 @@ struct System::Impl {
         return app_loader->ReadTitle(out);
     }
 
+    void AddGlueRegistrationForProcess(Loader::AppLoader& loader, Kernel::Process& process) {
+        std::vector<u8> nacp_data;
+        FileSys::NACP nacp;
+        if (loader.ReadControlData(nacp) == Loader::ResultStatus::Success) {
+            nacp_data = nacp.GetRawBytes();
+        } else {
+            nacp_data.resize(sizeof(FileSys::RawNACP));
+        }
+
+        Service::Glue::ApplicationLaunchProperty launch{};
+        launch.title_id = process.GetTitleID();
+
+        FileSys::PatchManager pm{launch.title_id};
+        launch.version = pm.GetGameVersion().value_or(0);
+
+        // TODO(DarkLordZach): When FSController/Game Card Support is added, if
+        // current_process_game_card use correct StorageId
+        launch.base_game_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry(
+            launch.title_id, FileSys::ContentRecordType::Program));
+        launch.update_storage_id = GetStorageIdForFrontendSlot(content_provider->GetSlotForEntry(
+            FileSys::GetUpdateTitleID(launch.title_id), FileSys::ContentRecordType::Program));
+
+        arp_manager.Register(launch.title_id, launch, std::move(nacp_data));
+    }
+
     void SetStatus(ResultStatus new_status, const char* details = nullptr) {
         status = new_status;
         if (details) {
@@ -249,6 +304,9 @@ struct System::Impl {
     /// Frontend applets
     Service::AM::Applets::AppletManager applet_manager;
 
+    /// Glue services
+    Service::Glue::ARPManager arp_manager;
+
     /// Service manager
     std::shared_ptr<Service::SM::ServiceManager> service_manager;
 
@@ -500,6 +558,14 @@ const Reporter& System::GetReporter() const {
     return impl->reporter;
 }
 
+Service::Glue::ARPManager& System::GetARPManager() {
+    return impl->arp_manager;
+}
+
+const Service::Glue::ARPManager& System::GetARPManager() const {
+    return impl->arp_manager;
+}
+
 System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
     return impl->Init(*this, emu_window);
 }
diff --git a/src/core/core.h b/src/core/core.h
index 226ef4630..70adb7af9 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -43,6 +43,10 @@ struct AppletFrontendSet;
 class AppletManager;
 } // namespace AM::Applets
 
+namespace Glue {
+class ARPManager;
+}
+
 namespace SM {
 class ServiceManager;
 } // namespace SM
@@ -288,6 +292,10 @@ public:
 
     const Reporter& GetReporter() const;
 
+    Service::Glue::ARPManager& GetARPManager();
+
+    const Service::Glue::ARPManager& GetARPManager() const;
+
 private:
     System();
 
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index da823c37b..a8f80e2c6 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -493,6 +493,16 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
     return out;
 }
 
+std::optional<u32> PatchManager::GetGameVersion() const {
+    const auto& installed = Core::System::GetInstance().GetContentProvider();
+    const auto update_tid = GetUpdateTitleID(title_id);
+    if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
+        return installed.GetEntryVersion(update_tid);
+    }
+
+    return installed.GetEntryVersion(title_id);
+}
+
 std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
     const auto& installed = Core::System::GetInstance().GetContentProvider();
 
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 769f8c6f0..a363c6577 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -66,8 +66,13 @@ public:
     std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
         VirtualFile update_raw = nullptr) const;
 
-    // Given title_id of the program, attempts to get the control data of the update and parse it,
-    // falling back to the base control data.
+    // If the game update exists, returns the u32 version field in its Meta-type NCA. If that fails,
+    // it will fallback to the Meta-type NCA of the base game. If that fails, the result will be
+    // std::nullopt
+    std::optional<u32> GetGameVersion() const;
+
+    // Given title_id of the program, attempts to get the control data of the update and parse
+    // it, falling back to the base control data.
     std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
 
     // Version of GetControlMetadata that takes an arbitrary NCA
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 58917e094..4608490e0 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -645,6 +645,20 @@ ContentProviderUnion::ListEntriesFilterOrigin(std::optional<ContentProviderUnion
     return out;
 }
 
+std::optional<ContentProviderUnionSlot> ContentProviderUnion::GetSlotForEntry(
+    u64 title_id, ContentRecordType type) const {
+    const auto iter =
+        std::find_if(providers.begin(), providers.end(), [title_id, type](const auto& provider) {
+            return provider.second != nullptr && provider.second->HasEntry(title_id, type);
+        });
+
+    if (iter == providers.end()) {
+        return std::nullopt;
+    }
+
+    return iter->first;
+}
+
 ManualContentProvider::~ManualContentProvider() = default;
 
 void ManualContentProvider::AddEntry(TitleType title_type, ContentRecordType content_type,
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index ec9052653..4398d63e1 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -199,6 +199,9 @@ public:
         std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
         std::optional<u64> title_id = {}) const;
 
+    std::optional<ContentProviderUnionSlot> GetSlotForEntry(u64 title_id,
+                                                            ContentRecordType type) const;
+
 private:
     std::map<ContentProviderUnionSlot, ContentProvider*> providers;
 };
diff --git a/src/core/hle/service/arp/arp.cpp b/src/core/hle/service/arp/arp.cpp
deleted file mode 100644
index e675b0188..000000000
--- a/src/core/hle/service/arp/arp.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <memory>
-
-#include "common/logging/log.h"
-#include "core/hle/ipc_helpers.h"
-#include "core/hle/kernel/hle_ipc.h"
-#include "core/hle/service/arp/arp.h"
-#include "core/hle/service/service.h"
-#include "core/hle/service/sm/sm.h"
-
-namespace Service::ARP {
-
-class ARP_R final : public ServiceFramework<ARP_R> {
-public:
-    explicit ARP_R() : ServiceFramework{"arp:r"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, nullptr, "GetApplicationLaunchProperty"},
-            {1, nullptr, "GetApplicationLaunchPropertyWithApplicationId"},
-            {2, nullptr, "GetApplicationControlProperty"},
-            {3, nullptr, "GetApplicationControlPropertyWithApplicationId"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-};
-
-class IRegistrar final : public ServiceFramework<IRegistrar> {
-public:
-    explicit IRegistrar() : ServiceFramework{"IRegistrar"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, nullptr, "Issue"},
-            {1, nullptr, "SetApplicationLaunchProperty"},
-            {2, nullptr, "SetApplicationControlProperty"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-};
-
-class ARP_W final : public ServiceFramework<ARP_W> {
-public:
-    explicit ARP_W() : ServiceFramework{"arp:w"} {
-        // clang-format off
-        static const FunctionInfo functions[] = {
-            {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
-            {1, nullptr, "DeleteProperties"},
-        };
-        // clang-format on
-
-        RegisterHandlers(functions);
-    }
-
-private:
-    void AcquireRegistrar(Kernel::HLERequestContext& ctx) {
-        LOG_DEBUG(Service_ARP, "called");
-
-        IPC::ResponseBuilder rb{ctx, 2, 0, 1};
-        rb.Push(RESULT_SUCCESS);
-        rb.PushIpcInterface<IRegistrar>();
-    }
-};
-
-void InstallInterfaces(SM::ServiceManager& sm) {
-    std::make_shared<ARP_R>()->InstallAsService(sm);
-    std::make_shared<ARP_W>()->InstallAsService(sm);
-}
-
-} // namespace Service::ARP
diff --git a/src/core/hle/service/arp/arp.h b/src/core/hle/service/arp/arp.h
deleted file mode 100644
index 9d100187c..000000000
--- a/src/core/hle/service/arp/arp.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-namespace Service::SM {
-class ServiceManager;
-}
-
-namespace Service::ARP {
-
-/// Registers all ARP services with the specified service manager.
-void InstallInterfaces(SM::ServiceManager& sm);
-
-} // namespace Service::ARP
diff --git a/src/core/hle/service/glue/arp.cpp b/src/core/hle/service/glue/arp.cpp
new file mode 100644
index 000000000..b591ce31b
--- /dev/null
+++ b/src/core/hle/service/glue/arp.cpp
@@ -0,0 +1,297 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+
+#include "common/logging/log.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/glue/arp.h"
+#include "core/hle/service/glue/errors.h"
+#include "core/hle/service/glue/manager.h"
+#include "core/hle/service/service.h"
+
+namespace Service::Glue {
+
+namespace {
+std::optional<u64> GetTitleIDForProcessID(const Core::System& system, u64 process_id) {
+    const auto& list = system.Kernel().GetProcessList();
+    const auto iter = std::find_if(list.begin(), list.end(), [&process_id](const auto& process) {
+        return process->GetProcessID() == process_id;
+    });
+
+    if (iter == list.end()) {
+        return std::nullopt;
+    }
+
+    return (*iter)->GetTitleID();
+}
+} // Anonymous namespace
+
+ARP_R::ARP_R(const Core::System& system, const ARPManager& manager)
+    : ServiceFramework{"arp:r"}, system(system), manager(manager) {
+    // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, &ARP_R::GetApplicationLaunchProperty, "GetApplicationLaunchProperty"},
+            {1, &ARP_R::GetApplicationLaunchPropertyWithApplicationId, "GetApplicationLaunchPropertyWithApplicationId"},
+            {2, &ARP_R::GetApplicationControlProperty, "GetApplicationControlProperty"},
+            {3, &ARP_R::GetApplicationControlPropertyWithApplicationId, "GetApplicationControlPropertyWithApplicationId"},
+        };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+ARP_R::~ARP_R() = default;
+
+void ARP_R::GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto process_id = rp.PopRaw<u64>();
+
+    LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+    const auto title_id = GetTitleIDForProcessID(system, process_id);
+    if (!title_id.has_value()) {
+        LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ERR_NOT_REGISTERED);
+        return;
+    }
+
+    const auto res = manager.GetLaunchProperty(*title_id);
+
+    if (res.Failed()) {
+        LOG_ERROR(Service_ARP, "Failed to get launch property!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(res.Code());
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 6};
+    rb.Push(RESULT_SUCCESS);
+    rb.PushRaw(*res);
+}
+
+void ARP_R::GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto title_id = rp.PopRaw<u64>();
+
+    LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
+
+    const auto res = manager.GetLaunchProperty(title_id);
+
+    if (res.Failed()) {
+        LOG_ERROR(Service_ARP, "Failed to get launch property!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(res.Code());
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 6};
+    rb.Push(RESULT_SUCCESS);
+    rb.PushRaw(*res);
+}
+
+void ARP_R::GetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto process_id = rp.PopRaw<u64>();
+
+    LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+    const auto title_id = GetTitleIDForProcessID(system, process_id);
+    if (!title_id.has_value()) {
+        LOG_ERROR(Service_ARP, "Failed to get title ID for process ID!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ERR_NOT_REGISTERED);
+        return;
+    }
+
+    const auto res = manager.GetControlProperty(*title_id);
+
+    if (res.Failed()) {
+        LOG_ERROR(Service_ARP, "Failed to get control property!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(res.Code());
+        return;
+    }
+
+    ctx.WriteBuffer(*res);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(RESULT_SUCCESS);
+}
+
+void ARP_R::GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto title_id = rp.PopRaw<u64>();
+
+    LOG_DEBUG(Service_ARP, "called, title_id={:016X}", title_id);
+
+    const auto res = manager.GetControlProperty(title_id);
+
+    if (res.Failed()) {
+        LOG_ERROR(Service_ARP, "Failed to get control property!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(res.Code());
+        return;
+    }
+
+    ctx.WriteBuffer(*res);
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(RESULT_SUCCESS);
+}
+
+class IRegistrar final : public ServiceFramework<IRegistrar> {
+    friend class ARP_W;
+
+public:
+    explicit IRegistrar(
+        std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issuer)
+        : ServiceFramework{"IRegistrar"}, issue_process_id(std::move(issuer)) {
+        // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, &IRegistrar::Issue, "Issue"},
+            {1, &IRegistrar::SetApplicationLaunchProperty, "SetApplicationLaunchProperty"},
+            {2, &IRegistrar::SetApplicationControlProperty, "SetApplicationControlProperty"},
+        };
+        // clang-format on
+
+        RegisterHandlers(functions);
+    }
+
+private:
+    void Issue(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto process_id = rp.PopRaw<u64>();
+
+        LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+        if (process_id == 0) {
+            LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERR_INVALID_PROCESS_ID);
+            return;
+        }
+
+        if (issued) {
+            LOG_ERROR(Service_ARP,
+                      "Attempted to issue registrar, but registrar is already issued!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERR_INVALID_ACCESS);
+            return;
+        }
+
+        issue_process_id(process_id, launch, std::move(control));
+        issued = true;
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void SetApplicationLaunchProperty(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_ARP, "called");
+
+        if (issued) {
+            LOG_ERROR(
+                Service_ARP,
+                "Attempted to set application launch property, but registrar is already issued!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERR_INVALID_ACCESS);
+            return;
+        }
+
+        IPC::RequestParser rp{ctx};
+        launch = rp.PopRaw<ApplicationLaunchProperty>();
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void SetApplicationControlProperty(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_ARP, "called");
+
+        if (issued) {
+            LOG_ERROR(
+                Service_ARP,
+                "Attempted to set application control property, but registrar is already issued!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERR_INVALID_ACCESS);
+            return;
+        }
+
+        control = ctx.ReadBuffer();
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    std::function<ResultCode(u64, ApplicationLaunchProperty, std::vector<u8>)> issue_process_id;
+    bool issued = false;
+    ApplicationLaunchProperty launch;
+    std::vector<u8> control;
+};
+
+ARP_W::ARP_W(const Core::System& system, ARPManager& manager)
+    : ServiceFramework{"arp:w"}, system(system), manager(manager) {
+    // clang-format off
+        static const FunctionInfo functions[] = {
+            {0, &ARP_W::AcquireRegistrar, "AcquireRegistrar"},
+            {1, &ARP_W::DeleteProperties, "DeleteProperties"},
+        };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+ARP_W::~ARP_W() = default;
+
+void ARP_W::AcquireRegistrar(Kernel::HLERequestContext& ctx) {
+    LOG_DEBUG(Service_ARP, "called");
+
+    registrar = std::make_shared<IRegistrar>(
+        [this](u64 process_id, ApplicationLaunchProperty launch, std::vector<u8> control) {
+            const auto res = GetTitleIDForProcessID(system, process_id);
+            if (!res.has_value()) {
+                return ERR_NOT_REGISTERED;
+            }
+
+            return manager.Register(*res, launch, std::move(control));
+        });
+
+    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+    rb.Push(RESULT_SUCCESS);
+    rb.PushIpcInterface(registrar);
+}
+
+void ARP_W::DeleteProperties(Kernel::HLERequestContext& ctx) {
+    IPC::RequestParser rp{ctx};
+    const auto process_id = rp.PopRaw<u64>();
+
+    LOG_DEBUG(Service_ARP, "called, process_id={:016X}", process_id);
+
+    if (process_id == 0) {
+        LOG_ERROR(Service_ARP, "Must have non-zero process ID!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ERR_INVALID_PROCESS_ID);
+        return;
+    }
+
+    const auto title_id = GetTitleIDForProcessID(system, process_id);
+
+    if (!title_id.has_value()) {
+        LOG_ERROR(Service_ARP, "No title ID for process ID!");
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(ERR_NOT_REGISTERED);
+        return;
+    }
+
+    IPC::ResponseBuilder rb{ctx, 2};
+    rb.Push(manager.Unregister(*title_id));
+}
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/arp.h b/src/core/hle/service/glue/arp.h
new file mode 100644
index 000000000..d5f8a7e7a
--- /dev/null
+++ b/src/core/hle/service/glue/arp.h
@@ -0,0 +1,43 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::Glue {
+
+class ARPManager;
+class IRegistrar;
+
+class ARP_R final : public ServiceFramework<ARP_R> {
+public:
+    explicit ARP_R(const Core::System& system, const ARPManager& manager);
+    ~ARP_R() override;
+
+private:
+    void GetApplicationLaunchProperty(Kernel::HLERequestContext& ctx);
+    void GetApplicationLaunchPropertyWithApplicationId(Kernel::HLERequestContext& ctx);
+    void GetApplicationControlProperty(Kernel::HLERequestContext& ctx);
+    void GetApplicationControlPropertyWithApplicationId(Kernel::HLERequestContext& ctx);
+
+    const Core::System& system;
+    const ARPManager& manager;
+};
+
+class ARP_W final : public ServiceFramework<ARP_W> {
+public:
+    explicit ARP_W(const Core::System& system, ARPManager& manager);
+    ~ARP_W() override;
+
+private:
+    void AcquireRegistrar(Kernel::HLERequestContext& ctx);
+    void DeleteProperties(Kernel::HLERequestContext& ctx);
+
+    const Core::System& system;
+    ARPManager& manager;
+    std::shared_ptr<IRegistrar> registrar;
+};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/bgtc.cpp b/src/core/hle/service/glue/bgtc.cpp
new file mode 100644
index 000000000..cd89d088f
--- /dev/null
+++ b/src/core/hle/service/glue/bgtc.cpp
@@ -0,0 +1,50 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/glue/bgtc.h"
+
+namespace Service::Glue {
+
+BGTC_T::BGTC_T() : ServiceFramework{"bgtc:t"} {
+    // clang-format off
+    static const FunctionInfo functions[] = {
+        {1, nullptr, "NotifyTaskStarting"},
+        {2, nullptr, "NotifyTaskFinished"},
+        {3, nullptr, "GetTriggerEvent"},
+        {4, nullptr, "IsInHalfAwake"},
+        {5, nullptr, "NotifyClientName"},
+        {6, nullptr, "IsInFullAwake"},
+        {11, nullptr, "ScheduleTask"},
+        {12, nullptr, "GetScheduledTaskInterval"},
+        {13, nullptr, "UnscheduleTask"},
+        {14, nullptr, "GetScheduleEvent"},
+        {15, nullptr, "SchedulePeriodicTask"},
+        {101, nullptr, "GetOperationMode"},
+        {102, nullptr, "WillDisconnectNetworkWhenEnteringSleep"},
+        {103, nullptr, "WillStayHalfAwakeInsteadSleep"},
+    };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+BGTC_T::~BGTC_T() = default;
+
+BGTC_SC::BGTC_SC() : ServiceFramework{"bgtc:sc"} {
+    // clang-format off
+    static const FunctionInfo functions[] = {
+        {1, nullptr, "GetState"},
+        {2, nullptr, "GetStateChangedEvent"},
+        {3, nullptr, "NotifyEnteringHalfAwake"},
+        {4, nullptr, "NotifyLeavingHalfAwake"},
+        {5, nullptr, "SetIsUsingSleepUnsupportedDevices"},
+    };
+    // clang-format on
+
+    RegisterHandlers(functions);
+}
+
+BGTC_SC::~BGTC_SC() = default;
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/bgtc.h b/src/core/hle/service/glue/bgtc.h
new file mode 100644
index 000000000..81844f03e
--- /dev/null
+++ b/src/core/hle/service/glue/bgtc.h
@@ -0,0 +1,23 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::Glue {
+
+class BGTC_T final : public ServiceFramework<BGTC_T> {
+public:
+    BGTC_T();
+    ~BGTC_T() override;
+};
+
+class BGTC_SC final : public ServiceFramework<BGTC_SC> {
+public:
+    BGTC_SC();
+    ~BGTC_SC() override;
+};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/errors.h b/src/core/hle/service/glue/errors.h
new file mode 100644
index 000000000..c2874c585
--- /dev/null
+++ b/src/core/hle/service/glue/errors.h
@@ -0,0 +1,16 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::Glue {
+
+constexpr ResultCode ERR_INVALID_RESOURCE{ErrorModule::ARP, 0x1E};
+constexpr ResultCode ERR_INVALID_PROCESS_ID{ErrorModule::ARP, 0x1F};
+constexpr ResultCode ERR_INVALID_ACCESS{ErrorModule::ARP, 0x2A};
+constexpr ResultCode ERR_NOT_REGISTERED{ErrorModule::ARP, 0x66};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/glue.cpp b/src/core/hle/service/glue/glue.cpp
new file mode 100644
index 000000000..c728e815c
--- /dev/null
+++ b/src/core/hle/service/glue/glue.cpp
@@ -0,0 +1,25 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include "core/core.h"
+#include "core/hle/service/glue/arp.h"
+#include "core/hle/service/glue/bgtc.h"
+#include "core/hle/service/glue/glue.h"
+
+namespace Service::Glue {
+
+void InstallInterfaces(Core::System& system) {
+    // ARP
+    std::make_shared<ARP_R>(system, system.GetARPManager())
+        ->InstallAsService(system.ServiceManager());
+    std::make_shared<ARP_W>(system, system.GetARPManager())
+        ->InstallAsService(system.ServiceManager());
+
+    // BackGround Task Controller
+    std::make_shared<BGTC_T>()->InstallAsService(system.ServiceManager());
+    std::make_shared<BGTC_SC>()->InstallAsService(system.ServiceManager());
+}
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/glue.h b/src/core/hle/service/glue/glue.h
new file mode 100644
index 000000000..112cd238b
--- /dev/null
+++ b/src/core/hle/service/glue/glue.h
@@ -0,0 +1,16 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Service::Glue {
+
+/// Registers all Glue services with the specified service manager.
+void InstallInterfaces(Core::System& system);
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/manager.cpp b/src/core/hle/service/glue/manager.cpp
new file mode 100644
index 000000000..6da52d2d6
--- /dev/null
+++ b/src/core/hle/service/glue/manager.cpp
@@ -0,0 +1,78 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/glue/errors.h"
+#include "core/hle/service/glue/manager.h"
+
+namespace Service::Glue {
+
+struct ARPManager::MapEntry {
+    ApplicationLaunchProperty launch;
+    std::vector<u8> control;
+};
+
+ARPManager::ARPManager() = default;
+
+ARPManager::~ARPManager() = default;
+
+ResultVal<ApplicationLaunchProperty> ARPManager::GetLaunchProperty(u64 title_id) const {
+    if (title_id == 0) {
+        return ERR_INVALID_PROCESS_ID;
+    }
+
+    const auto iter = entries.find(title_id);
+    if (iter == entries.end()) {
+        return ERR_NOT_REGISTERED;
+    }
+
+    return MakeResult<ApplicationLaunchProperty>(iter->second.launch);
+}
+
+ResultVal<std::vector<u8>> ARPManager::GetControlProperty(u64 title_id) const {
+    if (title_id == 0) {
+        return ERR_INVALID_PROCESS_ID;
+    }
+
+    const auto iter = entries.find(title_id);
+    if (iter == entries.end()) {
+        return ERR_NOT_REGISTERED;
+    }
+
+    return MakeResult<std::vector<u8>>(iter->second.control);
+}
+
+ResultCode ARPManager::Register(u64 title_id, ApplicationLaunchProperty launch,
+                                std::vector<u8> control) {
+    if (title_id == 0) {
+        return ERR_INVALID_PROCESS_ID;
+    }
+
+    const auto iter = entries.find(title_id);
+    if (iter != entries.end()) {
+        return ERR_INVALID_ACCESS;
+    }
+
+    entries.insert_or_assign(title_id, MapEntry{launch, std::move(control)});
+    return RESULT_SUCCESS;
+}
+
+ResultCode ARPManager::Unregister(u64 title_id) {
+    if (title_id == 0) {
+        return ERR_INVALID_PROCESS_ID;
+    }
+
+    const auto iter = entries.find(title_id);
+    if (iter == entries.end()) {
+        return ERR_NOT_REGISTERED;
+    }
+
+    entries.erase(iter);
+    return RESULT_SUCCESS;
+}
+
+void ARPManager::ResetAll() {
+    entries.clear();
+}
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/glue/manager.h b/src/core/hle/service/glue/manager.h
new file mode 100644
index 000000000..a7f5ce3ee
--- /dev/null
+++ b/src/core/hle/service/glue/manager.h
@@ -0,0 +1,63 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include "common/common_types.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/romfs_factory.h"
+#include "core/hle/result.h"
+
+namespace Service::Glue {
+
+struct ApplicationLaunchProperty {
+    u64 title_id;
+    u32 version;
+    FileSys::StorageId base_game_storage_id;
+    FileSys::StorageId update_storage_id;
+    u8 program_index;
+    u8 reserved;
+};
+static_assert(sizeof(ApplicationLaunchProperty) == 0x10,
+              "ApplicationLaunchProperty has incorrect size.");
+
+// A class to manage state related to the arp:w and arp:r services, specifically the registration
+// and unregistration of launch and control properties.
+class ARPManager {
+public:
+    ARPManager();
+    ~ARPManager();
+
+    // Returns the ApplicationLaunchProperty corresponding to the provided title ID if it was
+    // previously registered, otherwise ERR_NOT_REGISTERED if it was never registered or
+    // ERR_INVALID_PROCESS_ID if the title ID is 0.
+    ResultVal<ApplicationLaunchProperty> GetLaunchProperty(u64 title_id) const;
+
+    // Returns a vector of the raw bytes of NACP data (necessarily 0x4000 in size) corresponding to
+    // the provided title ID if it was previously registered, otherwise ERR_NOT_REGISTERED if it was
+    // never registered or ERR_INVALID_PROCESS_ID if the title ID is 0.
+    ResultVal<std::vector<u8>> GetControlProperty(u64 title_id) const;
+
+    // Adds a new entry to the internal database with the provided parameters, returning
+    // ERR_INVALID_ACCESS if attempting to re-register a title ID without an intermediate Unregister
+    // step, and ERR_INVALID_PROCESS_ID if the title ID is 0.
+    ResultCode Register(u64 title_id, ApplicationLaunchProperty launch, std::vector<u8> control);
+
+    // Removes the registration for the provided title ID from the database, returning
+    // ERR_NOT_REGISTERED if it doesn't exist in the database and ERR_INVALID_PROCESS_ID if the
+    // title ID is 0.
+    ResultCode Unregister(u64 title_id);
+
+    // Removes all entries from the database, always succeeds. Should only be used when resetting
+    // system state.
+    void ResetAll();
+
+private:
+    struct MapEntry;
+    std::map<u64, MapEntry> entries;
+};
+
+} // namespace Service::Glue
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index b2954eb34..beae9c510 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -19,7 +19,6 @@
 #include "core/hle/service/am/am.h"
 #include "core/hle/service/aoc/aoc_u.h"
 #include "core/hle/service/apm/apm.h"
-#include "core/hle/service/arp/arp.h"
 #include "core/hle/service/audio/audio.h"
 #include "core/hle/service/bcat/module.h"
 #include "core/hle/service/bpc/bpc.h"
@@ -33,6 +32,7 @@
 #include "core/hle/service/fgm/fgm.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/hle/service/friend/friend.h"
+#include "core/hle/service/glue/glue.h"
 #include "core/hle/service/grc/grc.h"
 #include "core/hle/service/hid/hid.h"
 #include "core/hle/service/lbl/lbl.h"
@@ -207,7 +207,6 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
     AM::InstallInterfaces(*sm, nv_flinger);
     AOC::InstallInterfaces(*sm);
     APM::InstallInterfaces(*sm);
-    ARP::InstallInterfaces(*sm);
     Audio::InstallInterfaces(*sm);
     BCAT::InstallInterfaces(*sm);
     BPC::InstallInterfaces(*sm);
@@ -221,6 +220,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system,
     FGM::InstallInterfaces(*sm);
     FileSystem::InstallInterfaces(*sm, vfs);
     Friend::InstallInterfaces(*sm);
+    Glue::InstallInterfaces(system);
     GRC::InstallInterfaces(*sm);
     HID::InstallInterfaces(*sm);
     LBL::InstallInterfaces(*sm);