diff --git a/src/core/core.cpp b/src/core/core.cpp
index 6c32154dbaa4906763ce18def5538af835a8f80d..6d5b5a2d01fa50f26f1aa6a5d26f811148e3fcf7 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -185,7 +185,7 @@ struct System::Impl {
             LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
             return ResultStatus::ErrorGetLoader;
         }
-        std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode =
+        std::pair<std::optional<u32>, Loader::ResultStatus> system_mode =
             app_loader->LoadKernelSystemMode();
 
         if (system_mode.second != Loader::ResultStatus::Success) {
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 89ae79eb30a2a38215a0a81bd1e67825a3cd9046..904afa03956b1123478a7b9debe990a623f97ebc 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -141,28 +141,28 @@ Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source)
     return mac_key;
 }
 
-boost::optional<Key128> DeriveSDSeed() {
+std::optional<Key128> DeriveSDSeed() {
     const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
                                        "/system/save/8000000000000043",
                                    "rb+");
     if (!save_43.IsOpen())
-        return boost::none;
+        return {};
 
     const FileUtil::IOFile sd_private(
         FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
     if (!sd_private.IsOpen())
-        return boost::none;
+        return {};
 
     std::array<u8, 0x10> private_seed{};
     if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
-        return boost::none;
+        return {};
     }
 
     std::array<u8, 0x10> buffer{};
     std::size_t offset = 0;
     for (; offset + 0x10 < save_43.GetSize(); ++offset) {
         if (!save_43.Seek(offset, SEEK_SET)) {
-            return boost::none;
+            return {};
         }
 
         save_43.ReadBytes(buffer.data(), buffer.size());
@@ -172,12 +172,12 @@ boost::optional<Key128> DeriveSDSeed() {
     }
 
     if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
-        return boost::none;
+        return {};
     }
 
     Key128 seed{};
     if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
-        return boost::none;
+        return {};
     }
     return seed;
 }
@@ -291,26 +291,26 @@ static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
 }
 
 template <size_t size>
-static boost::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
+static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
     u64 offset = 0;
     for (size_t i = 0x20; i < data.size() - 0x10; ++i) {
         if (data[i] == 0x1) {
             offset = i + 1;
             break;
         } else if (data[i] != 0x0) {
-            return boost::none;
+            return {};
         }
     }
 
     return offset;
 }
 
-boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
-                                                       const RSAKeyPair<2048>& key) {
+std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+                                                     const RSAKeyPair<2048>& key) {
     u32 cert_authority;
     std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
     if (cert_authority == 0)
-        return boost::none;
+        return {};
     if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
         LOG_INFO(Crypto,
                  "Attempting to parse ticket with non-standard certificate authority {:08X}.",
@@ -321,7 +321,7 @@ boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
     std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
 
     if (rights_id == Key128{})
-        return boost::none;
+        return {};
 
     Key128 key_temp{};
 
@@ -356,17 +356,17 @@ boost::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
     std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
 
     if (m_0 != 0)
-        return boost::none;
+        return {};
 
     m_1 = m_1 ^ MGF1<0x20>(m_2);
     m_2 = m_2 ^ MGF1<0xDF>(m_1);
 
     const auto offset = FindTicketOffset(m_2);
-    if (offset == boost::none)
-        return boost::none;
-    ASSERT(offset.get() > 0);
+    if (!offset)
+        return {};
+    ASSERT(*offset > 0);
 
-    std::memcpy(key_temp.data(), m_2.data() + offset.get(), key_temp.size());
+    std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
 
     return std::make_pair(rights_id, key_temp);
 }
@@ -661,8 +661,8 @@ void KeyManager::DeriveSDSeedLazy() {
         return;
 
     const auto res = DeriveSDSeed();
-    if (res != boost::none)
-        SetKey(S128KeyType::SDSeed, res.get());
+    if (res)
+        SetKey(S128KeyType::SDSeed, *res);
 }
 
 static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
@@ -889,9 +889,9 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
 
     for (const auto& raw : res) {
         const auto pair = ParseTicket(raw, rsa_key);
-        if (pair == boost::none)
+        if (!pair)
             continue;
-        const auto& [rid, key] = pair.value();
+        const auto& [rid, key] = *pair;
         u128 rights_id;
         std::memcpy(rights_id.data(), rid.data(), rid.size());
         SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index cccb3c0aec092fa8e35bbc6e094b7aca627bd04c..22f268c6552686759a19cc9c04938cb864909b5d 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -6,9 +6,10 @@
 
 #include <array>
 #include <map>
+#include <optional>
 #include <string>
+
 #include <boost/container/flat_map.hpp>
-#include <boost/optional.hpp>
 #include <fmt/format.h>
 #include "common/common_types.h"
 #include "core/crypto/partition_data_manager.h"
@@ -191,14 +192,14 @@ Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master
 std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob,
                                     const Key128& key);
 
-boost::optional<Key128> DeriveSDSeed();
+std::optional<Key128> DeriveSDSeed();
 Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
 
 std::vector<TicketRaw> 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)
-boost::optional<std::pair<Key128, Key128>> ParseTicket(
-    const TicketRaw& ticket, const RSAKeyPair<2048>& eticket_extended_key);
+std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+                                                     const RSAKeyPair<2048>& eticket_extended_key);
 
 } // namespace Core::Crypto
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 77e04704ee99812ae33784cad7bfcfac7de313aa..b46fe893cfd1de6b75ab529af93a085b95e3f349 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -4,10 +4,9 @@
 
 #include <algorithm>
 #include <cstring>
+#include <optional>
 #include <utility>
 
-#include <boost/optional.hpp>
-
 #include "common/logging/log.h"
 #include "core/crypto/aes_util.h"
 #include "core/crypto/ctr_encryption_layer.h"
@@ -306,18 +305,18 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
         subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
         subsection_buckets.back().entries.push_back({size, {0}, 0});
 
-        boost::optional<Core::Crypto::Key128> key = boost::none;
+        std::optional<Core::Crypto::Key128> key = {};
         if (encrypted) {
             if (has_rights_id) {
                 status = Loader::ResultStatus::Success;
                 key = GetTitlekey();
-                if (key == boost::none) {
+                if (!key) {
                     status = Loader::ResultStatus::ErrorMissingTitlekey;
                     return false;
                 }
             } else {
                 key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
-                if (key == boost::none) {
+                if (!key) {
                     status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
                     return false;
                 }
@@ -332,7 +331,7 @@ bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTabl
         auto bktr = std::make_shared<BKTR>(
             bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
             relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
-            encrypted ? key.get() : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
+            encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
             section.raw.section_ctr);
 
         // BKTR applies to entire IVFC, so make an offset version to level 6
@@ -388,11 +387,11 @@ u8 NCA::GetCryptoRevision() const {
     return master_key_id;
 }
 
-boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
+std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
     const auto master_key_id = GetCryptoRevision();
 
     if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
-        return boost::none;
+        return {};
 
     std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
     Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -416,25 +415,25 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
     return out;
 }
 
-boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
+std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
     const auto master_key_id = GetCryptoRevision();
 
     u128 rights_id{};
     memcpy(rights_id.data(), header.rights_id.data(), 16);
     if (rights_id == u128{}) {
         status = Loader::ResultStatus::ErrorInvalidRightsID;
-        return boost::none;
+        return {};
     }
 
     auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
     if (titlekey == Core::Crypto::Key128{}) {
         status = Loader::ResultStatus::ErrorMissingTitlekey;
-        return boost::none;
+        return {};
     }
 
     if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
         status = Loader::ResultStatus::ErrorMissingTitlekek;
-        return boost::none;
+        return {};
     }
 
     Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -458,25 +457,25 @@ VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 s
     case NCASectionCryptoType::BKTR:
         LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
         {
-            boost::optional<Core::Crypto::Key128> key = boost::none;
+            std::optional<Core::Crypto::Key128> key = {};
             if (has_rights_id) {
                 status = Loader::ResultStatus::Success;
                 key = GetTitlekey();
-                if (key == boost::none) {
+                if (!key) {
                     if (status == Loader::ResultStatus::Success)
                         status = Loader::ResultStatus::ErrorMissingTitlekey;
                     return nullptr;
                 }
             } else {
                 key = GetKeyAreaKey(NCASectionCryptoType::CTR);
-                if (key == boost::none) {
+                if (!key) {
                     status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
                     return nullptr;
                 }
             }
 
-            auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
-                std::move(in), key.value(), starting_offset);
+            auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
+                                                                          starting_offset);
             std::vector<u8> iv(16);
             for (u8 i = 0; i < 8; ++i)
                 iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 211946686420b0f926f586c606beeab23abe5ebb..4bba5560713498c01661ad8fc67ac5e6bb1f87e4 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -6,9 +6,10 @@
 
 #include <array>
 #include <memory>
+#include <optional>
 #include <string>
 #include <vector>
-#include <boost/optional.hpp>
+
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
@@ -111,8 +112,8 @@ private:
     bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
 
     u8 GetCryptoRevision() const;
-    boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
-    boost::optional<Core::Crypto::Key128> GetTitlekey();
+    std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
+    std::optional<Core::Crypto::Key128> GetTitlekey();
     VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
 
     std::vector<VirtualDir> dirs;
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index 999939d5ae26c4fd89c0550a680615911c22ee52..485c4913a51be4f8f5ce3a5b13c5368810948ac2 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -103,12 +103,12 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
             offset += sizeof(u16);
 
             const auto data = ips->ReadByte(offset++);
-            if (data == boost::none)
+            if (!data)
                 return nullptr;
 
             if (real_offset + rle_size > in_data.size())
                 rle_size = static_cast<u16>(in_data.size() - real_offset);
-            std::memset(in_data.data() + real_offset, data.get(), rle_size);
+            std::memset(in_data.data() + real_offset, *data, rle_size);
         } else { // Standard Patch
             auto read = data_size;
             if (real_offset + read > in_data.size())
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index cb457b987e130671e36ca7934d08fd4c620a052d..0c1156989da8728a515a2290e6190d0a4f1edc3f 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -65,7 +65,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
     if (update != nullptr && update->GetExeFS() != nullptr &&
         update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
         LOG_INFO(Loader, "    ExeFS: Update ({}) applied successfully",
-                 FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
+                 FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
         exefs = update->GetExeFS();
     }
 
@@ -236,7 +236,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
         if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
             new_nca->GetRomFS() != nullptr) {
             LOG_INFO(Loader, "    RomFS: Update ({}) applied successfully",
-                     FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
+                     FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
             romfs = new_nca->GetRomFS();
         }
     } else if (update_raw != nullptr) {
@@ -280,12 +280,11 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
     } else {
         if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
             const auto meta_ver = installed->GetEntryVersion(update_tid);
-            if (meta_ver == boost::none || meta_ver.get() == 0) {
+            if (meta_ver.value_or(0) == 0) {
                 out.insert_or_assign("Update", "");
             } else {
                 out.insert_or_assign(
-                    "Update",
-                    FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
+                    "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
             }
         } else if (update_raw != nullptr) {
             out.insert_or_assign("Update", "PACKED");
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 29b1004141e1cf0c290cde920ff15122b3f0edf3..96302a241b4f40dcddeecb93ffc1e6cbd0794b0a 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -159,28 +159,28 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
     return file;
 }
 
-static boost::optional<NcaID> CheckMapForContentRecord(
+static std::optional<NcaID> CheckMapForContentRecord(
     const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
     if (map.find(title_id) == map.end())
-        return boost::none;
+        return {};
 
     const auto& cnmt = map.at(title_id);
 
     const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
                                    [type](const ContentRecord& rec) { return rec.type == type; });
     if (iter == cnmt.GetContentRecords().end())
-        return boost::none;
+        return {};
 
-    return boost::make_optional(iter->nca_id);
+    return std::make_optional(iter->nca_id);
 }
 
-boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
-                                                             ContentRecordType type) const {
+std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
+                                                           ContentRecordType type) const {
     if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
         return meta_id.at(title_id);
 
     const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
-    if (res1 != boost::none)
+    if (res1)
         return res1;
     return CheckMapForContentRecord(meta, title_id, type);
 }
@@ -283,17 +283,14 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
 
 VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
     const auto id = GetNcaIDFromMetadata(title_id, type);
-    if (id == boost::none)
-        return nullptr;
-
-    return GetFileAtID(id.get());
+    return id ? GetFileAtID(*id) : nullptr;
 }
 
 VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
     return GetEntryUnparsed(entry.title_id, entry.type);
 }
 
-boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
     const auto meta_iter = meta.find(title_id);
     if (meta_iter != meta.end())
         return meta_iter->second.GetTitleVersion();
@@ -302,15 +299,12 @@ boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
     if (yuzu_meta_iter != yuzu_meta.end())
         return yuzu_meta_iter->second.GetTitleVersion();
 
-    return boost::none;
+    return {};
 }
 
 VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
     const auto id = GetNcaIDFromMetadata(title_id, type);
-    if (id == boost::none)
-        return nullptr;
-
-    return parser(GetFileAtID(id.get()), id.get());
+    return id ? parser(GetFileAtID(*id), *id) : nullptr;
 }
 
 VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
@@ -364,8 +358,8 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
 }
 
 std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
-    boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
-    boost::optional<u64> title_id) const {
+    std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+    std::optional<u64> title_id) const {
     std::vector<RegisteredCacheEntry> out;
     IterateAllMetadata<RegisteredCacheEntry>(
         out,
@@ -373,11 +367,11 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
             return RegisteredCacheEntry{c.GetTitleID(), r.type};
         },
         [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
-            if (title_type != boost::none && title_type.get() != c.GetType())
+            if (title_type && *title_type != c.GetType())
                 return false;
-            if (record_type != boost::none && record_type.get() != r.type)
+            if (record_type && *record_type != r.type)
                 return false;
-            if (title_id != boost::none && title_id.get() != c.GetTitleID())
+            if (title_id && *title_id != c.GetTitleID())
                 return false;
             return true;
         });
@@ -459,7 +453,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType
 
 InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
                                              bool overwrite_if_exists,
-                                             boost::optional<NcaID> override_id) {
+                                             std::optional<NcaID> override_id) {
     const auto in = nca->GetBaseFile();
     Core::Crypto::SHA256Hash hash{};
 
@@ -468,12 +462,12 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
     // game is massive), we're going to cheat and only hash the first MB of the NCA.
     // Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
     NcaID id{};
-    if (override_id == boost::none) {
+    if (override_id) {
+        id = *override_id;
+    } else {
         const auto& data = in->ReadBytes(0x100000);
         mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
         memcpy(id.data(), hash.data(), 16);
-    } else {
-        id = override_id.get();
     }
 
     std::string path = GetRelativePathFromNcaID(id, false, true);
@@ -543,14 +537,14 @@ bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
     return HasEntry(entry.title_id, entry.type);
 }
 
-boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
     for (const auto& c : caches) {
         const auto res = c->GetEntryVersion(title_id);
-        if (res != boost::none)
+        if (res)
             return res;
     }
 
-    return boost::none;
+    return {};
 }
 
 VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
@@ -609,8 +603,8 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
 }
 
 std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
-    boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
-    boost::optional<u64> title_id) const {
+    std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+    std::optional<u64> title_id) const {
     std::vector<RegisteredCacheEntry> out;
     for (const auto& c : caches) {
         c->IterateAllMetadata<RegisteredCacheEntry>(
@@ -619,11 +613,11 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
                 return RegisteredCacheEntry{c.GetTitleID(), r.type};
             },
             [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
-                if (title_type != boost::none && title_type.get() != c.GetType())
+                if (title_type && *title_type != c.GetType())
                     return false;
-                if (record_type != boost::none && record_type.get() != r.type)
+                if (record_type && *record_type != r.type)
                     return false;
-                if (title_id != boost::none && title_id.get() != c.GetTitleID())
+                if (title_id && *title_id != c.GetTitleID())
                     return false;
                 return true;
             });
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 5beceffb3ec3477c90c1a712f82824fa0a05b1ee..6cfb16017583c246a10afcd26592d2691c5c38bd 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -84,7 +84,7 @@ public:
     bool HasEntry(u64 title_id, ContentRecordType type) const;
     bool HasEntry(RegisteredCacheEntry entry) const;
 
-    boost::optional<u32> GetEntryVersion(u64 title_id) const;
+    std::optional<u32> GetEntryVersion(u64 title_id) const;
 
     VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
     VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -96,11 +96,10 @@ public:
     std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
 
     std::vector<RegisteredCacheEntry> ListEntries() const;
-    // If a parameter is not boost::none, it will be filtered for from all entries.
+    // If a parameter is not std::nullopt, it will be filtered for from all entries.
     std::vector<RegisteredCacheEntry> ListEntriesFilter(
-        boost::optional<TitleType> title_type = boost::none,
-        boost::optional<ContentRecordType> record_type = boost::none,
-        boost::optional<u64> title_id = boost::none) const;
+        std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+        std::optional<u64> title_id = {}) const;
 
     // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
     // there is a meta NCA and all of them are accessible.
@@ -125,12 +124,11 @@ private:
     std::vector<NcaID> AccumulateFiles() const;
     void ProcessFiles(const std::vector<NcaID>& ids);
     void AccumulateYuzuMeta();
-    boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
+    std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
     VirtualFile GetFileAtID(NcaID id) const;
     VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
     InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
-                                bool overwrite_if_exists,
-                                boost::optional<NcaID> override_id = boost::none);
+                                bool overwrite_if_exists, std::optional<NcaID> override_id = {});
     bool RawInstallYuzuMeta(const CNMT& cnmt);
 
     VirtualDir dir;
@@ -153,7 +151,7 @@ public:
     bool HasEntry(u64 title_id, ContentRecordType type) const;
     bool HasEntry(RegisteredCacheEntry entry) const;
 
-    boost::optional<u32> GetEntryVersion(u64 title_id) const;
+    std::optional<u32> GetEntryVersion(u64 title_id) const;
 
     VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
     VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -165,11 +163,10 @@ public:
     std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
 
     std::vector<RegisteredCacheEntry> ListEntries() const;
-    // If a parameter is not boost::none, it will be filtered for from all entries.
+    // If a parameter is not std::nullopt, it will be filtered for from all entries.
     std::vector<RegisteredCacheEntry> ListEntriesFilter(
-        boost::optional<TitleType> title_type = boost::none,
-        boost::optional<ContentRecordType> record_type = boost::none,
-        boost::optional<u64> title_id = boost::none) const;
+        std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+        std::optional<u64> title_id = {}) const;
 
 private:
     std::vector<RegisteredCache*> caches;
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index 3824c74e0ddd28e036c475836bd4543d510e2fee..7b584de7fdf8438c63bf1a563cc3c47ddae67d79 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -167,13 +167,13 @@ std::string VfsFile::GetExtension() const {
 
 VfsDirectory::~VfsDirectory() = default;
 
-boost::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
+std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
     u8 out{};
     std::size_t size = Read(&out, 1, offset);
     if (size == 1)
         return out;
 
-    return boost::none;
+    return {};
 }
 
 std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 09dc9f28812eadf465f12fd2f425fbbb5bc5dd29..002f99d4e54db909a29e2cf561d22cdd1307ed5b 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -4,13 +4,15 @@
 
 #pragma once
 
+#include <functional>
 #include <map>
 #include <memory>
+#include <optional>
 #include <string>
 #include <string_view>
 #include <type_traits>
 #include <vector>
-#include <boost/optional.hpp>
+
 #include "common/common_types.h"
 #include "core/file_sys/vfs_types.h"
 
@@ -103,8 +105,8 @@ public:
     // into file. Returns number of bytes successfully written.
     virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
 
-    // Reads exactly one byte at the offset provided, returning boost::none on error.
-    virtual boost::optional<u8> ReadByte(std::size_t offset = 0) const;
+    // Reads exactly one byte at the offset provided, returning std::nullopt on error.
+    virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
     // Reads size bytes starting at offset in file into a vector.
     virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
     // Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index a4c6719a0d4cf9908b66bbc43561cc955d8e3dbb..c96f884888c29531538c807dee3a2dd7324654f8 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -57,11 +57,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
     return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
 }
 
-boost::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
+std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
     if (r_offset < size)
         return file->ReadByte(offset + r_offset);
 
-    return boost::none;
+    return {};
 }
 
 std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h
index 8062702a7a10b42486bdc91f17a53b170909ed21..f7b7a3256483ffc94efcdc46db4758fa49582260 100644
--- a/src/core/file_sys/vfs_offset.h
+++ b/src/core/file_sys/vfs_offset.h
@@ -29,7 +29,7 @@ public:
     bool IsReadable() const override;
     std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
     std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
-    boost::optional<u8> ReadByte(std::size_t offset) const override;
+    std::optional<u8> ReadByte(std::size_t offset) const override;
     std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
     std::vector<u8> ReadAllBytes() const override;
     bool WriteByte(u8 data, std::size_t offset) override;
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index 44fab51d1268cfbe8e4e6b179e748bafeacda56d..9f5a90b1b4997d94243fcb8b54d6af0f31d2096d 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.h
@@ -53,10 +53,10 @@ public:
         return 0;
     }
 
-    boost::optional<u8> ReadByte(std::size_t offset) const override {
+    std::optional<u8> ReadByte(std::size_t offset) const override {
         if (offset < size)
             return value;
-        return boost::none;
+        return {};
     }
 
     std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 59bc9e0af6d45c495e5da8d2db25d19b8fd191fc..dd5cd9ced255f6f4bd5562e8ea9dfb23141f4ea3 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -4,9 +4,9 @@
 
 #include <algorithm>
 #include <cinttypes>
+#include <optional>
 #include <vector>
 
-#include <boost/optional.hpp>
 #include <boost/range/algorithm_ext/erase.hpp>
 
 #include "common/assert.h"
@@ -94,7 +94,7 @@ void Thread::CancelWakeupTimer() {
     CoreTiming::UnscheduleEventThreadsafe(kernel.ThreadWakeupCallbackEventType(), callback_handle);
 }
 
-static boost::optional<s32> GetNextProcessorId(u64 mask) {
+static std::optional<s32> GetNextProcessorId(u64 mask) {
     for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
         if (mask & (1ULL << index)) {
             if (!Core::System::GetInstance().Scheduler(index).GetCurrentThread()) {
@@ -142,7 +142,7 @@ void Thread::ResumeFromWait() {
 
     status = ThreadStatus::Ready;
 
-    boost::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
+    std::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
     if (!new_processor_id) {
         new_processor_id = processor_id;
     }
@@ -369,7 +369,7 @@ void Thread::ChangeCore(u32 core, u64 mask) {
         return;
     }
 
-    boost::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
+    std::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
 
     if (!new_processor_id) {
         new_processor_id = processor_id;
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 3cac1b4ff04e1cb8b2d958792bb2946808afc5dc..c08394e4cd96563f007ca09b08ae169ce9a81f07 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -195,7 +195,7 @@ std::size_t ProfileManager::GetOpenUserCount() const {
 
 /// Checks if a user id exists in our profile manager
 bool ProfileManager::UserExists(UUID uuid) const {
-    return GetUserIndex(uuid) != std::nullopt;
+    return GetUserIndex(uuid).has_value();
 }
 
 bool ProfileManager::UserExistsIndex(std::size_t index) const {
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 59aafd61606e212a48b5a99c477a6570acda5875..ac3ff9f20c83979e143eb4f4576eb7845a80fe14 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -743,7 +743,7 @@ void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
 
     Account::ProfileManager profile_manager{};
     const auto uuid = profile_manager.GetUser(Settings::values.current_user);
-    ASSERT(uuid != std::nullopt);
+    ASSERT(uuid);
     params.current_user = uuid->uuid;
 
     IPC::ResponseBuilder rb{ctx, 2, 0, 1};
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index fd98d541d39bdd8e01dbcfcd2fcfd363a5518744..630ebbfc70697ecf76339adfb3587380d6608902 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -31,7 +31,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
     buffer_wait_event->Signal();
 }
 
-boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
+std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
     auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
         // Only consider free buffers. Buffers become free once again after they've been Acquired
         // and Released by the compositor, see the NVFlinger::Compose method.
@@ -44,7 +44,7 @@ boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
     });
 
     if (itr == queue.end()) {
-        return boost::none;
+        return {};
     }
 
     itr->status = Buffer::Status::Dequeued;
@@ -70,12 +70,12 @@ void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
     itr->crop_rect = crop_rect;
 }
 
-boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() {
+std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
     auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
         return buffer.status == Buffer::Status::Queued;
     });
     if (itr == queue.end())
-        return boost::none;
+        return {};
     itr->status = Buffer::Status::Acquired;
     return *itr;
 }
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
index 50b767732b8d7c396c67b195a43e8a4d80a2beef..2fe81a560acc22bd09177ccba5b594f2179c3b3c 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -4,8 +4,9 @@
 
 #pragma once
 
+#include <optional>
 #include <vector>
-#include <boost/optional.hpp>
+
 #include "common/common_funcs.h"
 #include "common/math_util.h"
 #include "common/swap.h"
@@ -73,11 +74,11 @@ public:
     };
 
     void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer);
-    boost::optional<u32> DequeueBuffer(u32 width, u32 height);
+    std::optional<u32> DequeueBuffer(u32 width, u32 height);
     const IGBPBuffer& RequestBuffer(u32 slot) const;
     void QueueBuffer(u32 slot, BufferTransformFlags transform,
                      const MathUtil::Rectangle<int>& crop_rect);
-    boost::optional<const Buffer&> AcquireBuffer();
+    std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
     void ReleaseBuffer(u32 slot);
     u32 Query(QueryType type);
 
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index d47b6f659bd4cfacad40357487bf50a938a4de19..214e6d1b35076c97289da13e70f22dcd1c761cc6 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -3,7 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
-#include <boost/optional.hpp>
+#include <optional>
 
 #include "common/alignment.h"
 #include "common/assert.h"
@@ -134,7 +134,7 @@ void NVFlinger::Compose() {
 
         MicroProfileFlip();
 
-        if (buffer == boost::none) {
+        if (!buffer) {
             auto& system_instance = Core::System::GetInstance();
 
             // There was no queued buffer to draw, render previous frame
@@ -143,7 +143,7 @@ void NVFlinger::Compose() {
             continue;
         }
 
-        auto& igbp_buffer = buffer->igbp_buffer;
+        auto& igbp_buffer = buffer->get().igbp_buffer;
 
         // Now send the buffer to the GPU for drawing.
         // TODO(Subv): Support more than just disp0. The display device selection is probably based
@@ -152,10 +152,10 @@ void NVFlinger::Compose() {
         ASSERT(nvdisp);
 
         nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format,
-                     igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform,
-                     buffer->crop_rect);
+                     igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
+                     buffer->get().transform, buffer->get().crop_rect);
 
-        buffer_queue->ReleaseBuffer(buffer->slot);
+        buffer_queue->ReleaseBuffer(buffer->get().slot);
     }
 }
 
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 184537daa479fe6bbebcb8700f8094e9d1481745..d764b24066626141d8015a6be2c0ebd8b59e2308 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -6,9 +6,10 @@
 #include <array>
 #include <cstring>
 #include <memory>
+#include <optional>
 #include <type_traits>
 #include <utility>
-#include <boost/optional.hpp>
+
 #include "common/alignment.h"
 #include "common/assert.h"
 #include "common/common_funcs.h"
@@ -506,9 +507,9 @@ private:
             IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
             const u32 width{request.data.width};
             const u32 height{request.data.height};
-            boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+            std::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
 
-            if (slot != boost::none) {
+            if (slot) {
                 // Buffer is available
                 IGBPDequeueBufferResponseParcel response{*slot};
                 ctx.WriteBuffer(response.Serialize());
@@ -520,7 +521,7 @@ private:
                         Kernel::ThreadWakeupReason reason) {
                         // Repeat TransactParcel DequeueBuffer when a buffer is available
                         auto buffer_queue = nv_flinger->GetBufferQueue(id);
-                        boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+                        std::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
                         IGBPDequeueBufferResponseParcel response{*slot};
                         ctx.WriteBuffer(response.Serialize());
                         IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index e562b3a0454032deeef6b26fe7be5622feeaa386..7686634bfd02d463e27c468fb4ac1ea2ed577000 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -6,10 +6,11 @@
 
 #include <iosfwd>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
-#include <boost/optional.hpp>
+
 #include "common/common_types.h"
 #include "core/file_sys/vfs.h"
 
@@ -145,7 +146,7 @@ public:
      * information.
      * @returns A pair with the optional system mode, and and the status.
      */
-    virtual std::pair<boost::optional<u32>, ResultStatus> LoadKernelSystemMode() {
+    virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() {
         // 96MB allocated to the application.
         return std::make_pair(2, ResultStatus::Success);
     }
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 014298ed6827abeab07bfe62cb88d768197ef22f..70abd856a85780317d8893612c6fd6c22c7ffbf6 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -4,9 +4,9 @@
 
 #include <algorithm>
 #include <cstring>
+#include <optional>
 #include <utility>
 
-#include <boost/optional.hpp>
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
diff --git a/src/core/memory_hook.h b/src/core/memory_hook.h
index 0269c7ff15e500704aebe5661a136a29fef6629e..940777107b049e09d0fd980e3c848170e915b5bc 100644
--- a/src/core/memory_hook.h
+++ b/src/core/memory_hook.h
@@ -5,7 +5,8 @@
 #pragma once
 
 #include <memory>
-#include <boost/optional.hpp>
+#include <optional>
+
 #include "common/common_types.h"
 
 namespace Memory {
@@ -18,19 +19,19 @@ namespace Memory {
  *
  * A hook may be mapped to multiple regions of memory.
  *
- * If a boost::none or false is returned from a function, the read/write request is passed through
+ * If a std::nullopt or false is returned from a function, the read/write request is passed through
  * to the underlying memory region.
  */
 class MemoryHook {
 public:
     virtual ~MemoryHook();
 
-    virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0;
+    virtual std::optional<bool> IsValidAddress(VAddr addr) = 0;
 
-    virtual boost::optional<u8> Read8(VAddr addr) = 0;
-    virtual boost::optional<u16> Read16(VAddr addr) = 0;
-    virtual boost::optional<u32> Read32(VAddr addr) = 0;
-    virtual boost::optional<u64> Read64(VAddr addr) = 0;
+    virtual std::optional<u8> Read8(VAddr addr) = 0;
+    virtual std::optional<u16> Read16(VAddr addr) = 0;
+    virtual std::optional<u32> Read32(VAddr addr) = 0;
+    virtual std::optional<u64> Read64(VAddr addr) = 0;
 
     virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0;
 
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 37e15bad0ee842f562c8d28ed89325f83f780365..9b8a44fa19bccadf036ec94f1c91ceb5c9ec0f3f 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -64,11 +64,11 @@ void TestEnvironment::ClearWriteRecords() {
 
 TestEnvironment::TestMemory::~TestMemory() {}
 
-boost::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
+std::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
     return true;
 }
 
-boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
+std::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
     const auto iter = data.find(addr);
 
     if (iter == data.end()) {
@@ -79,15 +79,15 @@ boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
     return iter->second;
 }
 
-boost::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
+std::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
     return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8;
 }
 
-boost::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
+std::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
     return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16;
 }
 
-boost::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
+std::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
     return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32;
 }
 
diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h
index 5de8dab4e5755d2518523ef26a630b70876bd621..0b75396017a560aa5a9d8f36369b91b1b8c3e4a3 100644
--- a/src/tests/core/arm/arm_test_common.h
+++ b/src/tests/core/arm/arm_test_common.h
@@ -64,12 +64,12 @@ private:
 
         ~TestMemory() override;
 
-        boost::optional<bool> IsValidAddress(VAddr addr) override;
+        std::optional<bool> IsValidAddress(VAddr addr) override;
 
-        boost::optional<u8> Read8(VAddr addr) override;
-        boost::optional<u16> Read16(VAddr addr) override;
-        boost::optional<u32> Read32(VAddr addr) override;
-        boost::optional<u64> Read64(VAddr addr) override;
+        std::optional<u8> Read8(VAddr addr) override;
+        std::optional<u16> Read16(VAddr addr) override;
+        std::optional<u32> Read32(VAddr addr) override;
+        std::optional<u64> Read64(VAddr addr) override;
 
         bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override;
 
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index f1aa6091b8efdf4f8a4efeeba16f24e01e6468bb..28e8c13aa2555d5032c89f9170363aa964f683a1 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -81,7 +81,7 @@ void GPU::ProcessCommandLists(const std::vector<CommandListHeader>& commands) {
     for (auto entry : commands) {
         Tegra::GPUVAddr address = entry.Address();
         u32 size = entry.sz;
-        const boost::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
+        const std::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
         VAddr current_addr = *head_address;
         while (current_addr < *head_address + size * sizeof(CommandHeader)) {
             const CommandHeader header = {Memory::Read32(current_addr)};
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 27ef865a2113c42a314aa77f9cd28de4e0382f59..7357d20d16830b51a1b54c0396943f26e32d5ef4 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -167,7 +167,7 @@ void Maxwell3D::ProcessQueryGet() {
     GPUVAddr sequence_address = regs.query.QueryAddress();
     // Since the sequence address is given as a GPU VAddr, we have to convert it to an application
     // VAddr before writing.
-    boost::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address);
+    std::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address);
 
     // TODO(Subv): Support the other query units.
     ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
@@ -285,7 +285,7 @@ void Maxwell3D::ProcessCBData(u32 value) {
     // Don't allow writing past the end of the buffer.
     ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
 
-    boost::optional<VAddr> address =
+    std::optional<VAddr> address =
         memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
 
     Memory::Write32(*address, value);
@@ -298,7 +298,7 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
     GPUVAddr tic_base_address = regs.tic.TICAddress();
 
     GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
-    boost::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
+    std::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
 
     Texture::TICEntry tic_entry;
     Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
@@ -322,7 +322,7 @@ Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
     GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
 
     GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
-    boost::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
+    std::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
 
     Texture::TSCEntry tsc_entry;
     Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
@@ -386,7 +386,7 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
 
     ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
 
-    boost::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
+    std::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
     Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
 
     Texture::FullTextureInfo tex_info{};
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 141b9159b7f0d110a18eb138dbf2e349af7f4f06..b84da512f3cebb399043d19afe7c211a5016173d 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -5,12 +5,11 @@
 #pragma once
 
 #include <bitset>
+#include <optional>
 #include <string>
 #include <tuple>
 #include <vector>
 
-#include <boost/optional.hpp>
-
 #include "common/assert.h"
 #include "common/bit_field.h"
 #include "common/common_types.h"
@@ -1456,7 +1455,7 @@ public:
         Type type;
     };
 
-    static boost::optional<const Matcher&> Decode(Instruction instr) {
+    static std::optional<std::reference_wrapper<const Matcher>> Decode(Instruction instr) {
         static const auto table{GetDecodeTable()};
 
         const auto matches_instruction = [instr](const auto& matcher) {
@@ -1464,7 +1463,8 @@ public:
         };
 
         auto iter = std::find_if(table.begin(), table.end(), matches_instruction);
-        return iter != table.end() ? boost::optional<const Matcher&>(*iter) : boost::none;
+        return iter != table.end() ? std::optional<std::reference_wrapper<const Matcher>>(*iter)
+                                   : std::nullopt;
     }
 
 private:
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index 377bd66ab000969287e2157d2dc097564cb32351..f6af132fbf12fcd9ef0542669259baa0b8f98ebd 100644
--- a/src/video_core/macro_interpreter.cpp
+++ b/src/video_core/macro_interpreter.cpp
@@ -29,7 +29,7 @@ void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> pa
 void MacroInterpreter::Reset() {
     registers = {};
     pc = 0;
-    delayed_pc = boost::none;
+    delayed_pc = {};
     method_address.raw = 0;
     parameters.clear();
     // The next parameter index starts at 1, because $r1 already has the value of the first
@@ -44,10 +44,10 @@ bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
     pc += 4;
 
     // Update the program counter if we were delayed
-    if (delayed_pc != boost::none) {
+    if (delayed_pc) {
         ASSERT(is_delay_slot);
         pc = *delayed_pc;
-        delayed_pc = boost::none;
+        delayed_pc = {};
     }
 
     switch (opcode.operation) {
diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h
index cee0baaf39206bfc9c5f09355a642cf7702e82f1..773684bdebef5d12a6633ec691b78d23a010181b 100644
--- a/src/video_core/macro_interpreter.h
+++ b/src/video_core/macro_interpreter.h
@@ -5,8 +5,9 @@
 #pragma once
 
 #include <array>
+#include <optional>
 #include <vector>
-#include <boost/optional.hpp>
+
 #include "common/bit_field.h"
 #include "common/common_types.h"
 
@@ -149,7 +150,7 @@ private:
     Engines::Maxwell3D& maxwell3d;
 
     u32 pc; ///< Current program counter
-    boost::optional<u32>
+    std::optional<u32>
         delayed_pc; ///< Program counter to execute at after the delay slot is executed.
 
     static constexpr std::size_t NumMacroRegisters = 8;
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 022d4ab74ea074f1b18cf0fa7bf9f34186e0077e..90a8e825d40cecaa1aecd5ce2ba1761ae88d95e5 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -9,7 +9,7 @@
 namespace Tegra {
 
 GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
-    boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, align);
+    std::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, align);
     ASSERT(gpu_addr);
 
     for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
@@ -34,7 +34,7 @@ GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
 }
 
 GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
-    boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, PAGE_SIZE);
+    std::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, PAGE_SIZE);
     ASSERT(gpu_addr);
 
     for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
@@ -97,7 +97,7 @@ GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
     return {};
 }
 
-boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
+std::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
     GPUVAddr gpu_addr = 0;
     u64 free_space = 0;
     align = (align + PAGE_MASK) & ~PAGE_MASK;
@@ -118,7 +118,7 @@ boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
     return {};
 }
 
-boost::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
+std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
     VAddr base_addr = PageSlot(gpu_addr);
 
     if (base_addr == static_cast<u64>(PageStatus::Allocated) ||
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index caf80093fcf807c3125ed911874d6612e8812978..b1255fd560f0cfa8284189b71157f3d88fb38902 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -6,10 +6,9 @@
 
 #include <array>
 #include <memory>
+#include <optional>
 #include <vector>
 
-#include <boost/optional.hpp>
-
 #include "common/common_types.h"
 
 namespace Tegra {
@@ -27,7 +26,7 @@ public:
     GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
     GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
     GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
-    boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
+    std::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
     std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
 
     static constexpr u64 PAGE_BITS = 16;
@@ -35,7 +34,7 @@ public:
     static constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
 
 private:
-    boost::optional<GPUVAddr> FindFreeBlock(u64 size, u64 align = 1);
+    std::optional<GPUVAddr> FindFreeBlock(u64 size, u64 align = 1);
     bool IsPageMapped(GPUVAddr gpu_addr);
     VAddr& PageSlot(GPUVAddr gpu_addr);
 
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 2cd0738ff72e1defb2e4f2fcfd7c9de0ed8dc700..669e26e15c61e568bf350dab338fe8be0ebd4124 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -6,7 +6,8 @@
 
 #include <atomic>
 #include <memory>
-#include <boost/optional.hpp>
+#include <optional>
+
 #include "common/common_types.h"
 #include "video_core/gpu.h"
 #include "video_core/rasterizer_interface.h"
@@ -28,7 +29,8 @@ public:
     virtual ~RendererBase();
 
     /// Swap buffers (render frame)
-    virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0;
+    virtual void SwapBuffers(
+        std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0;
 
     /// Initialize the renderer
     virtual bool Init() = 0;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index c142095c55b85d097701885168238f064033e17d..41a54b3e7e31b277ada92a512e3bcf8e2600f0f7 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -17,7 +17,7 @@ OGLBufferCache::OGLBufferCache(std::size_t size) : stream_buffer(GL_ARRAY_BUFFER
 GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size,
                                       std::size_t alignment, bool cache) {
     auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
-    const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
+    const std::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
 
     // Cache management is a big overhead, so only cache entries with a given size.
     // TODO: Figure out which size is the best for given games.
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
index ee1d9601b2fb17e0503b961d913aaf49f68bbb76..741f14bc338b7694b067de980766fe90ecddb18c 100644
--- a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
@@ -45,7 +45,7 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size
     auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
 
     auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
-    const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
+    const std::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
     const u8* source{Memory::GetPointer(*cpu_addr)};
 
     for (u32 primitive = 0; primitive < count / 4; ++primitive) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 7bb5544fc01e27d04f0e349450cfd66a28f07b18..bf381271e04e096a298cb332b3f72051272a5898 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -401,7 +401,7 @@ void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
 
 void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb,
                                              bool preserve_contents,
-                                             boost::optional<std::size_t> single_color_target) {
+                                             std::optional<std::size_t> single_color_target) {
     MICROPROFILE_SCOPE(OpenGL_Framebuffer);
     const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 7b0615125b5a2a8debfa7cee46467c2ec7de5b25..47097c56936ae6fdcf2c047402f802db435edb54 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -8,12 +8,12 @@
 #include <cstddef>
 #include <map>
 #include <memory>
+#include <optional>
 #include <tuple>
 #include <utility>
 #include <vector>
 
 #include <boost/icl/interval_map.hpp>
-#include <boost/optional.hpp>
 #include <boost/range/iterator_range.hpp>
 #include <glad/glad.h>
 
@@ -111,7 +111,7 @@ private:
      */
     void ConfigureFramebuffers(bool use_color_fb = true, bool using_depth_fb = true,
                                bool preserve_contents = true,
-                               boost::optional<std::size_t> single_color_target = {});
+                               std::optional<std::size_t> single_color_target = {});
 
     /*
      * Configures the current constbuffers to use for the draw command.
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index dcf6941b017ade31f1daf142ad30f820449fb0ec..d1f6ffe40adbdd42423a89073675332e06c719b7 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -3,12 +3,12 @@
 // Refer to the license.txt file included.
 
 #include <map>
+#include <optional>
 #include <set>
 #include <string>
 #include <string_view>
 #include <unordered_set>
 
-#include <boost/optional.hpp>
 #include <fmt/format.h>
 
 #include "common/assert.h"
@@ -144,7 +144,7 @@ private:
         for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
             const Instruction instr = {program_code[offset]};
             if (const auto opcode = OpCode::Decode(instr)) {
-                switch (opcode->GetId()) {
+                switch (opcode->get().GetId()) {
                 case OpCode::Id::EXIT: {
                     // The EXIT instruction can be predicated, which means that the shader can
                     // conditionally end on this instruction. We have to consider the case where the
@@ -430,7 +430,7 @@ public:
      */
     void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
                                     const Tegra::Shader::IpaMode& input_mode,
-                                    boost::optional<Register> vertex = {}) {
+                                    std::optional<Register> vertex = {}) {
         const std::string dest = GetRegisterAsFloat(reg);
         const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem);
         shader.AddLine(dest + " = " + src + ';');
@@ -807,10 +807,10 @@ private:
     /// Generates code representing an input attribute register.
     std::string GetInputAttribute(Attribute::Index attribute,
                                   const Tegra::Shader::IpaMode& input_mode,
-                                  boost::optional<Register> vertex = {}) {
+                                  std::optional<Register> vertex = {}) {
         auto GeometryPass = [&](const std::string& name) {
             if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) {
-                return "gs_" + name + '[' + GetRegisterAsInteger(vertex.value(), 0, false) + ']';
+                return "gs_" + name + '[' + GetRegisterAsInteger(*vertex, 0, false) + ']';
             }
             return name;
         };
@@ -1465,7 +1465,7 @@ private:
         }
 
         shader.AddLine(
-            fmt::format("// {}: {} (0x{:016x})", offset, opcode->GetName(), instr.value));
+            fmt::format("// {}: {} (0x{:016x})", offset, opcode->get().GetName(), instr.value));
 
         using Tegra::Shader::Pred;
         ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute,
@@ -1473,7 +1473,7 @@ private:
 
         // Some instructions (like SSY) don't have a predicate field, they are always
         // unconditionally executed.
-        bool can_be_predicated = OpCode::IsPredicatedInstruction(opcode->GetId());
+        bool can_be_predicated = OpCode::IsPredicatedInstruction(opcode->get().GetId());
 
         if (can_be_predicated && instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) {
             shader.AddLine("if (" +
@@ -1483,7 +1483,7 @@ private:
             ++shader.scope;
         }
 
-        switch (opcode->GetType()) {
+        switch (opcode->get().GetType()) {
         case OpCode::Type::Arithmetic: {
             std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
 
@@ -1500,7 +1500,7 @@ private:
                 }
             }
 
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::MOV_C:
             case OpCode::Id::MOV_R: {
                 // MOV does not have neither 'abs' nor 'neg' bits.
@@ -1600,14 +1600,15 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}",
+                             opcode->get().GetName());
                 UNREACHABLE();
             }
             }
             break;
         }
         case OpCode::Type::ArithmeticImmediate: {
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::MOV32_IMM: {
                 regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1);
                 break;
@@ -1651,7 +1652,7 @@ private:
             std::string op_a = instr.bfe.negate_a ? "-" : "";
             op_a += regs.GetRegisterAsInteger(instr.gpr8);
 
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::BFE_IMM: {
                 std::string inner_shift =
                     '(' + op_a + " << " + std::to_string(instr.bfe.GetLeftShiftValue()) + ')';
@@ -1663,7 +1664,7 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -1685,7 +1686,7 @@ private:
                 }
             }
 
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::SHR_C:
             case OpCode::Id::SHR_R:
             case OpCode::Id::SHR_IMM: {
@@ -1705,7 +1706,7 @@ private:
                 regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " << " + op_b, 1, 1);
                 break;
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -1715,7 +1716,7 @@ private:
             std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
             std::string op_b = std::to_string(instr.alu.imm20_32.Value());
 
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::IADD32I:
                 if (instr.iadd32i.negate_a)
                     op_a = "-(" + op_a + ')';
@@ -1737,7 +1738,7 @@ private:
             }
             default: {
                 LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}",
-                             opcode->GetName());
+                             opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -1757,7 +1758,7 @@ private:
                 }
             }
 
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::IADD_C:
             case OpCode::Id::IADD_R:
             case OpCode::Id::IADD_IMM: {
@@ -1793,7 +1794,7 @@ private:
                     }
                 };
 
-                if (opcode->GetId() == OpCode::Id::IADD3_R) {
+                if (opcode->get().GetId() == OpCode::Id::IADD3_R) {
                     apply_height(instr.iadd3.height_a, op_a);
                     apply_height(instr.iadd3.height_b, op_b);
                     apply_height(instr.iadd3.height_c, op_c);
@@ -1809,7 +1810,7 @@ private:
                     op_c = "-(" + op_c + ')';
 
                 std::string result;
-                if (opcode->GetId() == OpCode::Id::IADD3_R) {
+                if (opcode->get().GetId() == OpCode::Id::IADD3_R) {
                     switch (instr.iadd3.mode) {
                     case Tegra::Shader::IAdd3Mode::RightShift:
                         // TODO(tech4me): According to
@@ -1884,7 +1885,7 @@ private:
                 const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39);
                 std::string lut;
 
-                if (opcode->GetId() == OpCode::Id::LOP3_R) {
+                if (opcode->get().GetId() == OpCode::Id::LOP3_R) {
                     lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')';
                 } else {
                     lut = '(' + std::to_string(instr.alu.lop3.GetImmLut48()) + ')';
@@ -1914,7 +1915,7 @@ private:
             case OpCode::Id::LEA_HI: {
                 std::string op_c;
 
-                switch (opcode->GetId()) {
+                switch (opcode->get().GetId()) {
                 case OpCode::Id::LEA_R2: {
                     op_a = regs.GetRegisterAsInteger(instr.gpr20);
                     op_b = regs.GetRegisterAsInteger(instr.gpr39);
@@ -1959,7 +1960,8 @@ private:
                     op_b = regs.GetRegisterAsInteger(instr.gpr8);
                     op_a = std::to_string(instr.lea.imm.entry_a);
                     op_c = std::to_string(instr.lea.imm.entry_b);
-                    LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}", opcode->GetName());
+                    LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}",
+                                 opcode->get().GetName());
                     UNREACHABLE();
                 }
                 }
@@ -1974,7 +1976,7 @@ private:
             }
             default: {
                 LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}",
-                             opcode->GetName());
+                             opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -1982,20 +1984,21 @@ private:
             break;
         }
         case OpCode::Type::ArithmeticHalf: {
-            if (opcode->GetId() == OpCode::Id::HADD2_C || opcode->GetId() == OpCode::Id::HADD2_R) {
+            if (opcode->get().GetId() == OpCode::Id::HADD2_C ||
+                opcode->get().GetId() == OpCode::Id::HADD2_R) {
                 ASSERT_MSG(instr.alu_half.ftz == 0, "Unimplemented");
             }
             const bool negate_a =
-                opcode->GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0;
+                opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0;
             const bool negate_b =
-                opcode->GetId() != OpCode::Id::HMUL2_C && instr.alu_half.negate_b != 0;
+                opcode->get().GetId() != OpCode::Id::HMUL2_C && instr.alu_half.negate_b != 0;
 
             const std::string op_a =
                 GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.alu_half.type_a,
                              instr.alu_half.abs_a != 0, negate_a);
 
             std::string op_b;
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::HADD2_C:
             case OpCode::Id::HMUL2_C:
                 op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
@@ -2013,7 +2016,7 @@ private:
             op_b = GetHalfFloat(op_b, instr.alu_half.type_b, instr.alu_half.abs_b != 0, negate_b);
 
             const std::string result = [&]() {
-                switch (opcode->GetId()) {
+                switch (opcode->get().GetId()) {
                 case OpCode::Id::HADD2_C:
                 case OpCode::Id::HADD2_R:
                     return '(' + op_a + " + " + op_b + ')';
@@ -2021,7 +2024,8 @@ private:
                 case OpCode::Id::HMUL2_R:
                     return '(' + op_a + " * " + op_b + ')';
                 default:
-                    LOG_CRITICAL(HW_GPU, "Unhandled half float instruction: {}", opcode->GetName());
+                    LOG_CRITICAL(HW_GPU, "Unhandled half float instruction: {}",
+                                 opcode->get().GetName());
                     UNREACHABLE();
                     return std::string("0");
                 }
@@ -2032,7 +2036,7 @@ private:
             break;
         }
         case OpCode::Type::ArithmeticHalfImmediate: {
-            if (opcode->GetId() == OpCode::Id::HADD2_IMM) {
+            if (opcode->get().GetId() == OpCode::Id::HADD2_IMM) {
                 ASSERT_MSG(instr.alu_half_imm.ftz == 0, "Unimplemented");
             } else {
                 ASSERT_MSG(instr.alu_half_imm.precision == Tegra::Shader::HalfPrecision::None,
@@ -2046,7 +2050,7 @@ private:
             const std::string op_b = UnpackHalfImmediate(instr, true);
 
             const std::string result = [&]() {
-                switch (opcode->GetId()) {
+                switch (opcode->get().GetId()) {
                 case OpCode::Id::HADD2_IMM:
                     return op_a + " + " + op_b;
                 case OpCode::Id::HMUL2_IMM:
@@ -2072,7 +2076,7 @@ private:
             ASSERT_MSG(instr.ffma.tab5980_1 == 0, "FFMA tab5980_1({}) not implemented",
                        instr.ffma.tab5980_1.Value());
 
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::FFMA_CR: {
                 op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
                                         GLSLRegister::Type::Float);
@@ -2096,7 +2100,7 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -2107,14 +2111,14 @@ private:
             break;
         }
         case OpCode::Type::Hfma2: {
-            if (opcode->GetId() == OpCode::Id::HFMA2_RR) {
+            if (opcode->get().GetId() == OpCode::Id::HFMA2_RR) {
                 ASSERT_MSG(instr.hfma2.rr.precision == Tegra::Shader::HalfPrecision::None,
                            "Unimplemented");
             } else {
                 ASSERT_MSG(instr.hfma2.precision == Tegra::Shader::HalfPrecision::None,
                            "Unimplemented");
             }
-            const bool saturate = opcode->GetId() == OpCode::Id::HFMA2_RR
+            const bool saturate = opcode->get().GetId() == OpCode::Id::HFMA2_RR
                                       ? instr.hfma2.rr.saturate != 0
                                       : instr.hfma2.saturate != 0;
 
@@ -2122,7 +2126,7 @@ private:
                 GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hfma2.type_a);
             std::string op_b, op_c;
 
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::HFMA2_CR:
                 op_b = GetHalfFloat(regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
                                                     GLSLRegister::Type::UnsignedInteger),
@@ -2160,7 +2164,7 @@ private:
             break;
         }
         case OpCode::Type::Conversion: {
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::I2I_R: {
                 ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
 
@@ -2298,14 +2302,15 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}",
+                             opcode->get().GetName());
                 UNREACHABLE();
             }
             }
             break;
         }
         case OpCode::Type::Memory: {
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::LD_A: {
                 // Note: Shouldn't this be interp mode flat? As in no interpolation made.
                 ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex,
@@ -2949,7 +2954,7 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -3043,7 +3048,7 @@ private:
                              instr.hsetp2.abs_a, instr.hsetp2.negate_a);
 
             const std::string op_b = [&]() {
-                switch (opcode->GetId()) {
+                switch (opcode->get().GetId()) {
                 case OpCode::Id::HSETP2_R:
                     return GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
                                         instr.hsetp2.type_b, instr.hsetp2.abs_a,
@@ -3105,7 +3110,7 @@ private:
             break;
         }
         case OpCode::Type::PredicateSetPredicate: {
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::PSETP: {
                 const std::string op_a =
                     GetPredicateCondition(instr.psetp.pred12, instr.psetp.neg_pred12 != 0);
@@ -3151,7 +3156,8 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled predicate instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled predicate instruction: {}",
+                             opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -3239,7 +3245,7 @@ private:
                              instr.hset2.abs_a != 0, instr.hset2.negate_a != 0);
 
             const std::string op_b = [&]() {
-                switch (opcode->GetId()) {
+                switch (opcode->get().GetId()) {
                 case OpCode::Id::HSET2_R:
                     return GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
                                         instr.hset2.type_b, instr.hset2.abs_b != 0,
@@ -3288,7 +3294,7 @@ private:
             const bool is_signed{instr.xmad.sign_a == 1};
 
             bool is_merge{};
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::XMAD_CR: {
                 is_merge = instr.xmad.merge_56;
                 op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
@@ -3317,7 +3323,7 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled XMAD instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled XMAD instruction: {}", opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -3369,7 +3375,7 @@ private:
             break;
         }
         default: {
-            switch (opcode->GetId()) {
+            switch (opcode->get().GetId()) {
             case OpCode::Id::EXIT: {
                 if (stage == Maxwell3D::Regs::ShaderStage::Fragment) {
                     EmitFragmentOutputsWrite();
@@ -3564,7 +3570,7 @@ private:
                 break;
             }
             default: {
-                LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
+                LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->get().GetName());
                 UNREACHABLE();
             }
             }
@@ -3705,9 +3711,9 @@ std::string GetCommonDeclarations() {
                        RasterizerOpenGL::MaxConstbufferSize / sizeof(GLvec4));
 }
 
-boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
-                                                Maxwell3D::Regs::ShaderStage stage,
-                                                const std::string& suffix) {
+std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
+                                              Maxwell3D::Regs::ShaderStage stage,
+                                              const std::string& suffix) {
     try {
         const auto subroutines =
             ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines();
@@ -3716,7 +3722,7 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code,
     } catch (const DecompileFail& exception) {
         LOG_ERROR(HW_GPU, "Shader decompilation failed: {}", exception.what());
     }
-    return boost::none;
+    return {};
 }
 
 } // namespace OpenGL::GLShader::Decompiler
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index b20cc4bfaaa40a0ec227e7fc99dd5c965d677ecd..d01a4a7ee5ebb636f92b49c98a1f45585ffbe800 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -6,8 +6,8 @@
 
 #include <array>
 #include <functional>
+#include <optional>
 #include <string>
-#include <boost/optional.hpp>
 #include "common/common_types.h"
 #include "video_core/engines/maxwell_3d.h"
 #include "video_core/renderer_opengl/gl_shader_gen.h"
@@ -18,8 +18,8 @@ using Tegra::Engines::Maxwell3D;
 
 std::string GetCommonDeclarations();
 
-boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
-                                                Maxwell3D::Regs::ShaderStage stage,
-                                                const std::string& suffix);
+std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
+                                              Maxwell3D::Regs::ShaderStage stage,
+                                              const std::string& suffix);
 
 } // namespace OpenGL::GLShader::Decompiler
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index dfb56270670d1e61137ddd63e6f587f89cebfd9f..9d17edd63133860293d117a2a97fc9561404e9aa 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -37,7 +37,7 @@ layout(std140) uniform vs_config {
     ProgramResult program =
         Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
                                      Maxwell3D::Regs::ShaderStage::Vertex, "vertex")
-            .get_value_or({});
+            .value_or(ProgramResult());
 
     out += program.first;
 
@@ -45,7 +45,7 @@ layout(std140) uniform vs_config {
         ProgramResult program_b =
             Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
                                          Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
-                .get_value_or({});
+                .value_or(ProgramResult());
         out += program_b.first;
     }
 
@@ -90,7 +90,7 @@ ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
     ProgramResult program =
         Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
                                      Maxwell3D::Regs::ShaderStage::Geometry, "geometry")
-            .get_value_or({});
+            .value_or(ProgramResult());
     out += R"(
 out gl_PerVertex {
     vec4 gl_Position;
@@ -124,7 +124,7 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
     ProgramResult program =
         Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
                                      Maxwell3D::Regs::ShaderStage::Fragment, "fragment")
-            .get_value_or({});
+            .value_or(ProgramResult());
     out += R"(
 layout(location = 0) out vec4 FragColor0;
 layout(location = 1) out vec4 FragColor1;
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 90b68943d1fdfc714993a9aa7ab6a30167cf493b..ea38da93224edb898b8515488db96b64b26d3e41 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -115,7 +115,8 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& window)
 RendererOpenGL::~RendererOpenGL() = default;
 
 /// Swap buffers (render frame)
-void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) {
+void RendererOpenGL::SwapBuffers(
+    std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
     ScopeAcquireGLContext acquire_context{render_window};
 
     Core::System::GetInstance().GetPerfStats().EndSystemFrame();
@@ -124,11 +125,11 @@ void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&
     OpenGLState prev_state = OpenGLState::GetCurState();
     state.Apply();
 
-    if (framebuffer != boost::none) {
+    if (framebuffer) {
         // If framebuffer is provided, reload it from memory to a texture
-        if (screen_info.texture.width != (GLsizei)framebuffer->width ||
-            screen_info.texture.height != (GLsizei)framebuffer->height ||
-            screen_info.texture.pixel_format != framebuffer->pixel_format) {
+        if (screen_info.texture.width != (GLsizei)framebuffer->get().width ||
+            screen_info.texture.height != (GLsizei)framebuffer->get().height ||
+            screen_info.texture.pixel_format != framebuffer->get().pixel_format) {
             // Reallocate texture if the framebuffer size has changed.
             // This is expected to not happen very often and hence should not be a
             // performance problem.
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 961467a6201e3c6bcb9f35c554c1480b418793cd..c0868c0e473d7d85f0b6cf6e83101a755cd8c2c1 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -51,7 +51,8 @@ public:
     ~RendererOpenGL() override;
 
     /// Swap buffers (render frame)
-    void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override;
+    void SwapBuffers(
+        std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
 
     /// Initialize the renderer
     bool Init() override;
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 94789c0641fde5a287440cb3d6b497287165296f..42a7beac67dbbca1a3028ce75a3b700b36289a63 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -322,7 +322,7 @@ void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool a
     }
 
     updateButtonLabels();
-    input_setter = boost::none;
+    input_setter = {};
 }
 
 void ConfigureInput::keyPressEvent(QKeyEvent* event) {
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index d1198db8111b7ce17ee8696ba19bc2b05347ed60..32c7183f99b3a244178542b955a453163c8bd3fe 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -7,11 +7,13 @@
 #include <array>
 #include <functional>
 #include <memory>
+#include <optional>
 #include <string>
 #include <unordered_map>
+
 #include <QKeyEvent>
 #include <QWidget>
-#include <boost/optional.hpp>
+
 #include "common/param_package.h"
 #include "core/settings.h"
 #include "input_common/main.h"
@@ -41,7 +43,7 @@ private:
     std::unique_ptr<QTimer> poll_timer;
 
     /// This will be the the setting function when an input is awaiting configuration.
-    boost::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
+    std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
 
     std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
     std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 4b34c1e28af47bd6a8fa4bf75209ac34d684d400..bb7ae3da4421a04448af864c4eb1999afe8d399c 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -173,7 +173,7 @@ void ConfigureSystem::UpdateCurrentUser() {
     ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
 
     const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
-    ASSERT(current_user != std::nullopt);
+    ASSERT(current_user);
     const auto username = GetAccountUsername(*profile_manager, *current_user);
 
     scene->clear();
@@ -261,7 +261,7 @@ void ConfigureSystem::AddUser() {
 void ConfigureSystem::RenameUser() {
     const auto user = tree_view->currentIndex().row();
     const auto uuid = profile_manager->GetUser(user);
-    ASSERT(uuid != std::nullopt);
+    ASSERT(uuid);
 
     Service::Account::ProfileBase profile;
     if (!profile_manager->GetProfileBase(*uuid, profile))
@@ -297,7 +297,7 @@ void ConfigureSystem::RenameUser() {
 void ConfigureSystem::DeleteUser() {
     const auto index = tree_view->currentIndex().row();
     const auto uuid = profile_manager->GetUser(index);
-    ASSERT(uuid != std::nullopt);
+    ASSERT(uuid);
     const auto username = GetAccountUsername(*profile_manager, *uuid);
 
     const auto confirm = QMessageBox::question(
@@ -324,7 +324,7 @@ void ConfigureSystem::DeleteUser() {
 void ConfigureSystem::SetUserImage() {
     const auto index = tree_view->currentIndex().row();
     const auto uuid = profile_manager->GetUser(index);
-    ASSERT(uuid != std::nullopt);
+    ASSERT(uuid);
 
     const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
                                                    tr("JPEG Images (*.jpg *.jpeg)"));
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
index 44d423da2704536c42409fc5d92fb32c4c9b1dd9..0adbab27d15d7dd7d0a8247c6c116eb899793793 100644
--- a/src/yuzu/debugger/graphics/graphics_surface.cpp
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -382,7 +382,7 @@ void GraphicsSurfaceWidget::OnUpdate() {
     // TODO: Implement a good way to visualize alpha components!
 
     QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
-    boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
+    std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
 
     // TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
     // Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
@@ -444,7 +444,7 @@ void GraphicsSurfaceWidget::SaveSurface() {
             pixmap->save(&file, "PNG");
     } else if (selectedFilter == bin_filter) {
         auto& gpu = Core::System::GetInstance().GPU();
-        boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
+        std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
 
         const u8* buffer = Memory::GetPointer(*address);
         ASSERT_MSG(buffer != nullptr, "Memory not accessible");
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index b5bfa6741859ed82e0bd320c140e573fff52133d..c5a56cbfda24279a864df50d7f7f14ae6dc600ae 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -786,7 +786,7 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
         ASSERT(index != -1 && index < 8);
 
         const auto user_id = manager.GetUser(index);
-        ASSERT(user_id != std::nullopt);
+        ASSERT(user_id);
         path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
                                                                 FileSys::SaveDataType::SaveData,
                                                                 program_id, user_id->uuid, 0);
@@ -1560,7 +1560,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
     }
 }
 
-boost::optional<u64> GMainWindow::SelectRomFSDumpTarget(
+std::optional<u64> GMainWindow::SelectRomFSDumpTarget(
     const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
     const auto dlc_entries =
         installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
@@ -1587,7 +1587,7 @@ boost::optional<u64> GMainWindow::SelectRomFSDumpTarget(
             this, tr("Select RomFS Dump Target"),
             tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
         if (!ok) {
-            return boost::none;
+            return {};
         }
 
         return romfs_tids[list.indexOf(res)];
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 7c7c223e18f61c300902f334e05ab856d973faf5..af637d89e3bf18d0b61c39d361d653171d5751c4 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -5,12 +5,12 @@
 #pragma once
 
 #include <memory>
+#include <optional>
 #include <unordered_map>
 
 #include <QMainWindow>
 #include <QTimer>
 
-#include <boost/optional.hpp>
 #include "common/common_types.h"
 #include "core/core.h"
 #include "ui_main.h"
@@ -178,8 +178,7 @@ private slots:
     void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
 
 private:
-    boost::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&,
-                                               u64 program_id);
+    std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id);
     void UpdateStatusBar();
 
     Ui::MainWindow ui;