diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 6dd6333639..46aceec3d1 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -37,6 +37,7 @@
 namespace Core::Crypto {
 
 constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
+constexpr u64 FULL_TICKET_SIZE = 0x400;
 
 using namespace Common;
 
@@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
     {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
 };
 
+namespace {
+template <std::size_t Size>
+bool IsAllZeroArray(const std::array<u8, Size>& array) {
+    return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
+}
+} // namespace
+
+u64 GetSignatureTypeDataSize(SignatureType type) {
+    switch (type) {
+    case SignatureType::RSA_4096_SHA1:
+    case SignatureType::RSA_4096_SHA256:
+        return 0x200;
+    case SignatureType::RSA_2048_SHA1:
+    case SignatureType::RSA_2048_SHA256:
+        return 0x100;
+    case SignatureType::ECDSA_SHA1:
+    case SignatureType::ECDSA_SHA256:
+        return 0x3C;
+    }
+    UNREACHABLE();
+}
+
+u64 GetSignatureTypePaddingSize(SignatureType type) {
+    switch (type) {
+    case SignatureType::RSA_4096_SHA1:
+    case SignatureType::RSA_4096_SHA256:
+    case SignatureType::RSA_2048_SHA1:
+    case SignatureType::RSA_2048_SHA256:
+        return 0x3C;
+    case SignatureType::ECDSA_SHA1:
+    case SignatureType::ECDSA_SHA256:
+        return 0x40;
+    }
+    UNREACHABLE();
+}
+
+SignatureType Ticket::GetSignatureType() const {
+    if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+        return ticket->sig_type;
+    }
+    if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+        return ticket->sig_type;
+    }
+    if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+        return ticket->sig_type;
+    }
+
+    UNREACHABLE();
+}
+
+TicketData& Ticket::GetData() {
+    if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+        return ticket->data;
+    }
+    if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+        return ticket->data;
+    }
+    if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+        return ticket->data;
+    }
+
+    UNREACHABLE();
+}
+
+const TicketData& Ticket::GetData() const {
+    if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
+        return ticket->data;
+    }
+    if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
+        return ticket->data;
+    }
+    if (auto ticket = std::get_if<ECDSATicket>(&data)) {
+        return ticket->data;
+    }
+
+    UNREACHABLE();
+}
+
+u64 Ticket::GetSize() const {
+    const auto sig_type = GetSignatureType();
+
+    return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) +
+           GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData);
+}
+
+Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) {
+    RSA2048Ticket out{};
+    out.sig_type = SignatureType::RSA_2048_SHA256;
+    out.data.rights_id = rights_id;
+    out.data.title_key_common = title_key;
+    return Ticket{out};
+}
+
 Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
     Key128 out{};
 
@@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
     }
 }
 
+RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
+    if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
+        return {};
+
+    const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
+
+    std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10);
+    std::array<u8, 0x230> extended_dec{};
+    AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
+    rsa_1.SetIV(extended_iv);
+    rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
+                    extended_dec.data(), Op::Decrypt);
+
+    RSAKeyPair<2048> rsa_key{};
+    std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
+    std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
+    std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
+
+    return rsa_key;
+}
+
 Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
     AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
     Key128 mac_key{};
@@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
     return Loader::ResultStatus::Success;
 }
 
-std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
+std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
     if (!ticket_save.IsOpen())
         return {};
 
@@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
         return {};
     }
 
-    std::vector<TicketRaw> out;
+    std::vector<Ticket> out;
     for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
         if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
             buffer[offset + 3] == 0x0) {
             out.emplace_back();
             auto& next = out.back();
-            std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
-            offset += next.size();
+            std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
+            offset += FULL_TICKET_SIZE;
         }
     }
 
@@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
     return offset;
 }
 
-std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
                                                      const RSAKeyPair<2048>& key) {
-    u32 cert_authority;
-    std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
-    if (cert_authority == 0)
+    const auto issuer = ticket.GetData().issuer;
+    if (issuer == std::array<u8, 0x40>{})
         return {};
-    if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
-        LOG_INFO(Crypto,
-                 "Attempting to parse ticket with non-standard certificate authority {:08X}.",
-                 cert_authority);
+    if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
+        LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
     }
 
-    Key128 rights_id;
-    std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
+    Key128 rights_id = ticket.GetData().rights_id;
 
     if (rights_id == Key128{})
         return {};
 
-    Key128 key_temp{};
-
-    if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
-        std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
-        return std::make_pair(rights_id, key_temp);
+    if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
+                     ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
+        return std::make_pair(rights_id, ticket.GetData().title_key_common);
     }
 
     mbedtls_mpi D; // RSA Private Exponent
@@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
 
     mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
     mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
-    mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
+    mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
 
     mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
 
@@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
         return {};
     ASSERT(*offset > 0);
 
+    Key128 key_temp{};
     std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
 
     return std::make_pair(rights_id, key_temp);
@@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
 
                 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
                 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
+            } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
+                eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
             } else {
                 for (const auto& kv : KEYS_VARIABLE_LENGTH) {
                     if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
@@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
     // Titlekeys
     data.DecryptProdInfo(GetBISKey(0));
 
-    const auto eticket_extended_kek = data.GetETicketExtendedKek();
+    eticket_extended_kek = data.GetETicketExtendedKek();
+    WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
+    PopulateTickets();
+}
 
-    std::vector<u8> extended_iv(0x10);
-    std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
-    std::array<u8, 0x230> extended_dec{};
-    AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
-    rsa_1.SetIV(extended_iv);
-    rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
-                    extended_dec.data(), Op::Decrypt);
+void KeyManager::PopulateTickets() {
+    const auto rsa_key = GetETicketRSAKey();
 
-    RSAKeyPair<2048> rsa_key{};
-    std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
-    std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
-    std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
+    if (rsa_key == RSAKeyPair<2048>{})
+        return;
+
+    if (!common_tickets.empty() && !personal_tickets.empty())
+        return;
 
     const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
                                      "/system/save/80000000000000e1",
@@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
 
     const auto blob2 = GetTicketblob(save2);
     auto res = GetTicketblob(save1);
+    const auto idx = res.size();
     res.insert(res.end(), blob2.begin(), blob2.end());
 
-    for (const auto& raw : res) {
-        const auto pair = ParseTicket(raw, rsa_key);
+    for (std::size_t i = 0; i < res.size(); ++i) {
+        const auto common = i < idx;
+        const auto pair = ParseTicket(res[i], rsa_key);
         if (!pair)
             continue;
         const auto& [rid, key] = *pair;
         u128 rights_id;
         std::memcpy(rights_id.data(), rid.data(), rid.size());
+
+        if (common) {
+            common_tickets[rights_id] = res[i];
+        } else {
+            personal_tickets[rights_id] = res[i];
+        }
+
         SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
     }
 }
 
+void KeyManager::SynthesizeTickets() {
+    for (const auto& key : s128_keys) {
+        if (key.first.type != S128KeyType::Titlekey) {
+            continue;
+        }
+        u128 rights_id{key.first.field1, key.first.field2};
+        Key128 rights_id_2;
+        std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size());
+        const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2);
+        common_tickets.insert_or_assign(rights_id, ticket);
+    }
+}
+
 void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
     if (key == Key128{})
         return;
@@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
     DeriveBase();
 }
 
+const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
+    return common_tickets;
+}
+
+const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
+    return personal_tickets;
+}
+
+bool KeyManager::AddTicketCommon(Ticket raw) {
+    const auto rsa_key = GetETicketRSAKey();
+    if (rsa_key == RSAKeyPair<2048>{})
+        return false;
+
+    const auto pair = ParseTicket(raw, rsa_key);
+    if (!pair)
+        return false;
+    const auto& [rid, key] = *pair;
+    u128 rights_id;
+    std::memcpy(rights_id.data(), rid.data(), rid.size());
+    common_tickets[rights_id] = raw;
+    SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+    return true;
+}
+
+bool KeyManager::AddTicketPersonalized(Ticket raw) {
+    const auto rsa_key = GetETicketRSAKey();
+    if (rsa_key == RSAKeyPair<2048>{})
+        return false;
+
+    const auto pair = ParseTicket(raw, rsa_key);
+    if (!pair)
+        return false;
+    const auto& [rid, key] = *pair;
+    u128 rights_id;
+    std::memcpy(rights_id.data(), rid.data(), rid.size());
+    common_tickets[rights_id] = raw;
+    SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+    return true;
+}
+
 const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
     {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
     {"eticket_rsa_kek_source",
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 22f268c655..7265c41714 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -9,8 +9,10 @@
 #include <optional>
 #include <string>
 
+#include <variant>
 #include <boost/container/flat_map.hpp>
 #include <fmt/format.h>
+#include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "core/crypto/partition_data_manager.h"
 #include "core/file_sys/vfs_types.h"
@@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
 using Key128 = std::array<u8, 0x10>;
 using Key256 = std::array<u8, 0x20>;
 using SHA256Hash = std::array<u8, 0x20>;
-using TicketRaw = std::array<u8, 0x400>;
+
+enum class SignatureType {
+    RSA_4096_SHA1 = 0x10000,
+    RSA_2048_SHA1 = 0x10001,
+    ECDSA_SHA1 = 0x10002,
+    RSA_4096_SHA256 = 0x10003,
+    RSA_2048_SHA256 = 0x10004,
+    ECDSA_SHA256 = 0x10005,
+};
+
+u64 GetSignatureTypeDataSize(SignatureType type);
+u64 GetSignatureTypePaddingSize(SignatureType type);
+
+enum class TitleKeyType : u8 {
+    Common = 0,
+    Personalized = 1,
+};
+
+struct TicketData {
+    std::array<u8, 0x40> issuer;
+    union {
+        std::array<u8, 0x100> title_key_block;
+
+        struct {
+            Key128 title_key_common;
+            std::array<u8, 0xF0> title_key_common_pad;
+        };
+    };
+
+    INSERT_PADDING_BYTES(0x1);
+    TitleKeyType type;
+    INSERT_PADDING_BYTES(0x3);
+    u8 revision;
+    INSERT_PADDING_BYTES(0xA);
+    u64 ticket_id;
+    u64 device_id;
+    std::array<u8, 0x10> rights_id;
+    u32 account_id;
+    INSERT_PADDING_BYTES(0x14C);
+};
+static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size.");
+
+struct RSA4096Ticket {
+    SignatureType sig_type;
+    std::array<u8, 0x200> sig_data;
+    INSERT_PADDING_BYTES(0x3C);
+    TicketData data;
+};
+
+struct RSA2048Ticket {
+    SignatureType sig_type;
+    std::array<u8, 0x100> sig_data;
+    INSERT_PADDING_BYTES(0x3C);
+    TicketData data;
+};
+
+struct ECDSATicket {
+    SignatureType sig_type;
+    std::array<u8, 0x3C> sig_data;
+    INSERT_PADDING_BYTES(0x40);
+    TicketData data;
+};
+
+struct Ticket {
+    std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
+
+    SignatureType GetSignatureType() const;
+    TicketData& GetData();
+    const TicketData& GetData() const;
+    u64 GetSize() const;
+
+    static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
+};
 
 static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
 static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
@@ -43,6 +117,19 @@ struct RSAKeyPair {
     std::array<u8, 4> exponent;
 };
 
+template <size_t bit_size, size_t byte_size>
+bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
+                const RSAKeyPair<bit_size, byte_size>& rhs) {
+    return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) ==
+           std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent);
+}
+
+template <size_t bit_size, size_t byte_size>
+bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
+                const RSAKeyPair<bit_size, byte_size>& rhs) {
+    return !(lhs == rhs);
+}
+
 enum class KeyCategory : u8 {
     Standard,
     Title,
@@ -151,22 +238,35 @@ public:
 
     static bool KeyFileExists(bool title);
 
-    // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save
-    // 8*43 and the private file to exist.
+    // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system
+    // save 8*43 and the private file to exist.
     void DeriveSDSeedLazy();
 
     bool BaseDeriveNecessary() const;
     void DeriveBase();
     void DeriveETicket(PartitionDataManager& data);
+    void PopulateTickets();
+    void SynthesizeTickets();
 
     void PopulateFromPartitionData(PartitionDataManager& data);
 
+    const std::map<u128, Ticket>& GetCommonTickets() const;
+    const std::map<u128, Ticket>& GetPersonalizedTickets() const;
+
+    bool AddTicketCommon(Ticket raw);
+    bool AddTicketPersonalized(Ticket raw);
+
 private:
     std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
     std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
 
+    // Map from rights ID to ticket
+    std::map<u128, Ticket> common_tickets;
+    std::map<u128, Ticket> personal_tickets;
+
     std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
     std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
+    std::array<u8, 576> eticket_extended_kek{};
 
     bool dev_mode;
     void LoadFromFile(const std::string& filename, bool is_title_keys);
@@ -178,6 +278,8 @@ private:
 
     void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
 
+    RSAKeyPair<2048> GetETicketRSAKey() const;
+
     void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
     void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
 
@@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
 std::optional<Key128> DeriveSDSeed();
 Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
 
-std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
+std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
 
-// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
-// 0x140-0x144 is zero)
-std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
+// (offset 0x140-0x144 is zero)
+std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
                                                      const RSAKeyPair<2048>& eticket_extended_key);
 
 } // namespace Core::Crypto
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index 6701cb9135..af70d174dc 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -2,32 +2,37 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "core/crypto/key_manager.h"
+#include "core/hle/ipc_helpers.h"
 #include "core/hle/service/service.h"
 
 namespace Service::ES {
 
+constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
+constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
+
 class ETicket final : public ServiceFramework<ETicket> {
 public:
     explicit ETicket() : ServiceFramework{"es"} {
         // clang-format off
         static const FunctionInfo functions[] = {
-            {1, nullptr, "ImportTicket"},
+            {1, &ETicket::ImportTicket, "ImportTicket"},
             {2, nullptr, "ImportTicketCertificateSet"},
             {3, nullptr, "DeleteTicket"},
             {4, nullptr, "DeletePersonalizedTicket"},
             {5, nullptr, "DeleteAllCommonTicket"},
             {6, nullptr, "DeleteAllPersonalizedTicket"},
             {7, nullptr, "DeleteAllPersonalizedTicketEx"},
-            {8, nullptr, "GetTitleKey"},
-            {9, nullptr, "CountCommonTicket"},
-            {10, nullptr, "CountPersonalizedTicket"},
-            {11, nullptr, "ListCommonTicket"},
-            {12, nullptr, "ListPersonalizedTicket"},
+            {8, &ETicket::GetTitleKey, "GetTitleKey"},
+            {9, &ETicket::CountCommonTicket, "CountCommonTicket"},
+            {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"},
+            {11, &ETicket::ListCommonTicket, "ListCommonTicket"},
+            {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"},
             {13, nullptr, "ListMissingPersonalizedTicket"},
-            {14, nullptr, "GetCommonTicketSize"},
-            {15, nullptr, "GetPersonalizedTicketSize"},
-            {16, nullptr, "GetCommonTicketData"},
-            {17, nullptr, "GetPersonalizedTicketData"},
+            {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"},
+            {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"},
+            {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"},
+            {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"},
             {18, nullptr, "OwnTicket"},
             {19, nullptr, "GetTicketInfo"},
             {20, nullptr, "ListLightTicketInfo"},
@@ -51,7 +56,212 @@ public:
         };
         // clang-format on
         RegisterHandlers(functions);
+
+        keys.PopulateTickets();
+        keys.SynthesizeTickets();
     }
+
+private:
+    bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) {
+        if (rights_id == u128{}) {
+            LOG_ERROR(Service_ETicket, "The rights ID was invalid!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERROR_INVALID_RIGHTS_ID);
+            return false;
+        }
+
+        return true;
+    }
+
+    void ImportTicket(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto ticket = ctx.ReadBuffer();
+        const auto cert = ctx.ReadBuffer(1);
+
+        if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
+            LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERROR_INVALID_ARGUMENT);
+            return;
+        }
+
+        Core::Crypto::Ticket raw{};
+        std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
+
+        if (!keys.AddTicketPersonalized(raw)) {
+            LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERROR_INVALID_ARGUMENT);
+            return;
+        }
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void GetTitleKey(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+        if (!CheckRightsId(ctx, rights_id))
+            return;
+
+        const auto key =
+            keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
+
+        if (key == Core::Crypto::Key128{}) {
+            LOG_ERROR(Service_ETicket,
+                      "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!");
+            IPC::ResponseBuilder rb{ctx, 2};
+            rb.Push(ERROR_INVALID_RIGHTS_ID);
+            return;
+        }
+
+        ctx.WriteBuffer(key.data(), key.size());
+
+        IPC::ResponseBuilder rb{ctx, 2};
+        rb.Push(RESULT_SUCCESS);
+    }
+
+    void CountCommonTicket(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_ETicket, "called");
+
+        const auto count = keys.GetCommonTickets().size();
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(count);
+    }
+
+    void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) {
+        LOG_DEBUG(Service_ETicket, "called");
+
+        const auto count = keys.GetPersonalizedTickets().size();
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(count);
+    }
+
+    void ListCommonTicket(Kernel::HLERequestContext& ctx) {
+        u32 out_entries;
+        if (keys.GetCommonTickets().empty())
+            out_entries = 0;
+        else
+            out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
+
+        LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
+
+        keys.PopulateTickets();
+        const auto tickets = keys.GetCommonTickets();
+        std::vector<u128> ids;
+        std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
+                       [](const auto& pair) { return pair.first; });
+
+        out_entries = std::min<u32>(ids.size(), out_entries);
+        ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(out_entries);
+    }
+
+    void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) {
+        u32 out_entries;
+        if (keys.GetPersonalizedTickets().empty())
+            out_entries = 0;
+        else
+            out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
+
+        LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
+
+        keys.PopulateTickets();
+        const auto tickets = keys.GetPersonalizedTickets();
+        std::vector<u128> ids;
+        std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
+                       [](const auto& pair) { return pair.first; });
+
+        out_entries = std::min<u32>(ids.size(), out_entries);
+        ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
+
+        IPC::ResponseBuilder rb{ctx, 3};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u32>(out_entries);
+    }
+
+    void GetCommonTicketSize(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+        if (!CheckRightsId(ctx, rights_id))
+            return;
+
+        const auto ticket = keys.GetCommonTickets().at(rights_id);
+
+        IPC::ResponseBuilder rb{ctx, 4};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u64>(ticket.GetSize());
+    }
+
+    void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+        if (!CheckRightsId(ctx, rights_id))
+            return;
+
+        const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
+
+        IPC::ResponseBuilder rb{ctx, 4};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u64>(ticket.GetSize());
+    }
+
+    void GetCommonTicketData(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+        if (!CheckRightsId(ctx, rights_id))
+            return;
+
+        const auto ticket = keys.GetCommonTickets().at(rights_id);
+
+        const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
+        ctx.WriteBuffer(&ticket, write_size);
+
+        IPC::ResponseBuilder rb{ctx, 4};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u64>(write_size);
+    }
+
+    void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) {
+        IPC::RequestParser rp{ctx};
+        const auto rights_id = rp.PopRaw<u128>();
+
+        LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
+
+        if (!CheckRightsId(ctx, rights_id))
+            return;
+
+        const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
+
+        const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
+        ctx.WriteBuffer(&ticket, write_size);
+
+        IPC::ResponseBuilder rb{ctx, 4};
+        rb.Push(RESULT_SUCCESS);
+        rb.Push<u64>(write_size);
+    }
+
+    Core::Crypto::KeyManager keys;
 };
 
 void InstallInterfaces(SM::ServiceManager& service_manager) {