diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp
index aaca9f1a3..425ea8efb 100644
--- a/src/citra_qt/configuration/configure_input.cpp
+++ b/src/citra_qt/configuration/configure_input.cpp
@@ -322,7 +322,7 @@ void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool a
     }
 
     updateButtonLabels();
-    input_setter = boost::none;
+    input_setter.reset();
 }
 
 void ConfigureInput::keyPressEvent(QKeyEvent* event) {
diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h
index 0e61dc00d..3bd489954 100644
--- a/src/citra_qt/configuration/configure_input.h
+++ b/src/citra_qt/configuration/configure_input.h
@@ -1,4 +1,4 @@
-// Copyright 2016 Citra Emulator Project
+// Copyright 2016 Citra Emulator Project
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
@@ -7,11 +7,11 @@
 #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"
@@ -42,7 +42,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/core/core.cpp b/src/core/core.cpp
index 9fb520fe5..91af11905 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -99,7 +99,7 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file
         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) {
@@ -116,7 +116,7 @@ System::ResultStatus System::Load(EmuWindow& emu_window, const std::string& file
         }
     }
 
-    ResultStatus init_result{Init(emu_window, system_mode.first.get())};
+    ResultStatus init_result{Init(emu_window, *system_mode.first)};
     if (init_result != ResultStatus::Success) {
         LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
                      static_cast<u32>(init_result));
diff --git a/src/core/file_sys/ticket.cpp b/src/core/file_sys/ticket.cpp
index 15a72116b..1f41d0fb2 100644
--- a/src/core/file_sys/ticket.cpp
+++ b/src/core/file_sys/ticket.cpp
@@ -38,13 +38,13 @@ Loader::ResultStatus Ticket::Load(const std::vector<u8> file_data, std::size_t o
     return Loader::ResultStatus::Success;
 }
 
-boost::optional<std::array<u8, 16>> Ticket::GetTitleKey() const {
+std::optional<std::array<u8, 16>> Ticket::GetTitleKey() const {
     HW::AES::InitKeys();
     std::array<u8, 16> ctr{};
     std::memcpy(ctr.data(), &ticket_body.title_id, sizeof(u64));
     HW::AES::SelectCommonKeyIndex(ticket_body.common_key_index);
     if (!HW::AES::IsNormalKeyAvailable(HW::AES::KeySlotID::TicketCommonKey)) {
-        return boost::none;
+        return {};
     }
     auto key = HW::AES::GetNormalKey(HW::AES::KeySlotID::TicketCommonKey);
     auto title_key = ticket_body.title_key;
diff --git a/src/core/file_sys/ticket.h b/src/core/file_sys/ticket.h
index f8700ae87..351a915c3 100644
--- a/src/core/file_sys/ticket.h
+++ b/src/core/file_sys/ticket.h
@@ -5,9 +5,9 @@
 #pragma once
 
 #include <array>
+#include <optional>
 #include <string>
 #include <vector>
-#include <boost/optional.hpp>
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
@@ -48,7 +48,7 @@ public:
 #pragma pack(pop)
 
     Loader::ResultStatus Load(const std::vector<u8> file_data, std::size_t offset = 0);
-    boost::optional<std::array<u8, 16>> GetTitleKey() const;
+    std::optional<std::array<u8, 16>> GetTitleKey() const;
 
 private:
     Body ticket_body;
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 59c7e39dd..94816d4ed 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -147,7 +147,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
 
     if (base_address == 0 && target_address == 0) {
         // Calculate the address at which to map the memory block.
-        target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address).value();
+        target_address = *Memory::PhysicalToVirtualAddress(linear_heap_phys_address);
     }
 
     // Map the memory block into the target process
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 05be6ef32..6fe160d74 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -131,8 +131,7 @@ ResultCode CIAFile::WriteTitleMetadata() {
     auto content_count = container.GetTitleMetadata().GetContentCount();
     content_written.resize(content_count);
 
-    auto title_key = container.GetTicket().GetTitleKey();
-    if (title_key) {
+    if (auto title_key = container.GetTicket().GetTitleKey()) {
         decryption_state->content.resize(content_count);
         for (std::size_t i = 0; i < content_count; ++i) {
             auto ctr = tmd.GetContentCTRByIndex(i);
@@ -339,7 +338,7 @@ InstallStatus InstallCIA(const std::string& path,
         Service::AM::CIAFile installFile(
             Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID()));
 
-        bool title_key_available = container.GetTicket().GetTitleKey().is_initialized();
+        bool title_key_available = container.GetTicket().GetTitleKey().has_value();
 
         for (std::size_t i = 0; i < container.GetTitleMetadata().GetContentCount(); i++) {
             if ((container.GetTitleMetadata().GetContentTypeByIndex(static_cast<u16>(i)) &
diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp
index 708adc27f..40829a327 100644
--- a/src/core/hle/service/apt/applet_manager.cpp
+++ b/src/core/hle/service/apt/applet_manager.cpp
@@ -207,7 +207,7 @@ ResultVal<MessageParameter> AppletManager::GlanceParameter(AppletId app_id) {
     // Note: The NS module always clears the DSPSleep and DSPWakeup signals even in GlanceParameter.
     if (next_parameter->signal == SignalType::DspSleep ||
         next_parameter->signal == SignalType::DspWakeup)
-        next_parameter = boost::none;
+        next_parameter = {};
 
     return MakeResult<MessageParameter>(parameter);
 }
@@ -216,7 +216,7 @@ ResultVal<MessageParameter> AppletManager::ReceiveParameter(AppletId app_id) {
     auto result = GlanceParameter(app_id);
     if (result.Succeeded()) {
         // Clear the parameter
-        next_parameter = boost::none;
+        next_parameter = {};
     }
     return result;
 }
@@ -236,7 +236,7 @@ bool AppletManager::CancelParameter(bool check_sender, AppletId sender_appid, bo
     }
 
     if (cancellation_success)
-        next_parameter = boost::none;
+        next_parameter = {};
 
     return cancellation_success;
 }
diff --git a/src/core/hle/service/apt/applet_manager.h b/src/core/hle/service/apt/applet_manager.h
index 32a39c213..d71920d80 100644
--- a/src/core/hle/service/apt/applet_manager.h
+++ b/src/core/hle/service/apt/applet_manager.h
@@ -5,8 +5,8 @@
 #pragma once
 
 #include <array>
+#include <optional>
 #include <vector>
-#include <boost/optional.hpp>
 #include "core/hle/kernel/event.h"
 #include "core/hle/result.h"
 #include "core/hle/service/fs/archive.h"
@@ -143,7 +143,7 @@ public:
 private:
     /// Parameter data to be returned in the next call to Glance/ReceiveParameter.
     /// TODO(Subv): Use std::optional once we migrate to C++17.
-    boost::optional<MessageParameter> next_parameter;
+    std::optional<MessageParameter> next_parameter;
 
     static constexpr std::size_t NumAppletSlot = 4;
 
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 682a57597..72202e7ba 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -206,7 +206,7 @@ void Module::Interface::GetSharedFont(Kernel::HLERequestContext& ctx) {
     // The shared font has to be relocated to the new address before being passed to the
     // application.
     VAddr target_address =
-        Memory::PhysicalToVirtualAddress(apt->shared_font_mem->linear_heap_phys_address).value();
+        *Memory::PhysicalToVirtualAddress(apt->shared_font_mem->linear_heap_phys_address);
     if (!apt->shared_font_relocated) {
         BCFNT::RelocateSharedFont(apt->shared_font_mem, target_address);
         apt->shared_font_relocated = true;
diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp
index 9f8a20fd9..b8e706f98 100644
--- a/src/core/hle/service/http_c.cpp
+++ b/src/core/hle/service/http_c.cpp
@@ -132,7 +132,7 @@ void HTTP_C::CreateContext(Kernel::HLERequestContext& ctx) {
     }
 
     // This command can only be called without a bound session.
-    if (session_data->current_http_context != boost::none) {
+    if (session_data->current_http_context) {
         LOG_ERROR(Service_HTTP, "Command called with a bound context");
 
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
@@ -198,7 +198,7 @@ void HTTP_C::CloseContext(Kernel::HLERequestContext& ctx) {
         return;
     }
 
-    ASSERT_MSG(session_data->current_http_context == boost::none,
+    ASSERT_MSG(!session_data->current_http_context,
                "Unimplemented CloseContext on context-bound session");
 
     auto itr = contexts.find(context_handle);
@@ -249,7 +249,7 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) {
     }
 
     // This command can only be called with a bound context
-    if (session_data->current_http_context == boost::none) {
+    if (!session_data->current_http_context) {
         LOG_ERROR(Service_HTTP, "Command called without a bound context");
 
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
@@ -263,7 +263,7 @@ void HTTP_C::AddRequestHeader(Kernel::HLERequestContext& ctx) {
         LOG_ERROR(Service_HTTP,
                   "Tried to add a request header on a mismatched session input context={} session "
                   "context={}",
-                  context_handle, session_data->current_http_context.get());
+                  context_handle, *session_data->current_http_context);
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
         rb.Push(ERROR_STATE_ERROR);
         rb.PushMappedBuffer(value_buffer);
@@ -313,7 +313,7 @@ void HTTP_C::OpenClientCertContext(Kernel::HLERequestContext& ctx) {
     if (!session_data->initialized) {
         LOG_ERROR(Service_HTTP, "Command called without Initialize");
         result = ERROR_STATE_ERROR;
-    } else if (session_data->current_http_context != boost::none) {
+    } else if (session_data->current_http_context) {
         LOG_ERROR(Service_HTTP, "Command called with a bound context");
         result = ERROR_NOT_IMPLEMENTED;
     } else if (session_data->num_client_certs >= 2) {
@@ -352,7 +352,7 @@ void HTTP_C::OpenDefaultClientCertContext(Kernel::HLERequestContext& ctx) {
         return;
     }
 
-    if (session_data->current_http_context != boost::none) {
+    if (session_data->current_http_context) {
         LOG_ERROR(Service_HTTP, "Command called with a bound context");
         IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
         rb.Push(ERROR_NOT_IMPLEMENTED);
diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h
index 2c452cc71..cf2df57ed 100644
--- a/src/core/hle/service/http_c.h
+++ b/src/core/hle/service/http_c.h
@@ -5,10 +5,10 @@
 #pragma once
 
 #include <memory>
+#include <optional>
 #include <string>
 #include <unordered_map>
 #include <vector>
-#include <boost/optional.hpp>
 #include "core/hle/kernel/shared_memory.h"
 #include "core/hle/service/service.h"
 
@@ -112,8 +112,8 @@ public:
     std::string url;
     RequestMethod method;
     RequestState state = RequestState::NotStarted;
-    boost::optional<Proxy> proxy;
-    boost::optional<BasicAuth> basic_auth;
+    std::optional<Proxy> proxy;
+    std::optional<BasicAuth> basic_auth;
     SSLConfig ssl_config{};
     u32 socket_buffer_size;
     std::vector<RequestHeader> headers;
@@ -123,7 +123,7 @@ public:
 struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
     /// The HTTP context that is currently bound to this session, this can be empty if no context
     /// has been bound. Certain commands can only be called on a session with a bound context.
-    boost::optional<Context::Handle> current_http_context;
+    std::optional<Context::Handle> current_http_context;
 
     u32 session_id;
 
diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp
index 63c088bf8..a81aa8ea1 100644
--- a/src/core/hw/aes/key.cpp
+++ b/src/core/hw/aes/key.cpp
@@ -4,8 +4,8 @@
 
 #include <algorithm>
 #include <exception>
+#include <optional>
 #include <sstream>
-#include <boost/optional.hpp>
 #include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -18,24 +18,24 @@ namespace AES {
 
 namespace {
 
-boost::optional<AESKey> generator_constant;
+std::optional<AESKey> generator_constant;
 
 struct KeySlot {
-    boost::optional<AESKey> x;
-    boost::optional<AESKey> y;
-    boost::optional<AESKey> normal;
+    std::optional<AESKey> x;
+    std::optional<AESKey> y;
+    std::optional<AESKey> normal;
 
-    void SetKeyX(boost::optional<AESKey> key) {
+    void SetKeyX(std::optional<AESKey> key) {
         x = key;
         GenerateNormalKey();
     }
 
-    void SetKeyY(boost::optional<AESKey> key) {
+    void SetKeyY(std::optional<AESKey> key) {
         y = key;
         GenerateNormalKey();
     }
 
-    void SetNormalKey(boost::optional<AESKey> key) {
+    void SetNormalKey(std::optional<AESKey> key) {
         normal = key;
     }
 
@@ -43,7 +43,7 @@ struct KeySlot {
         if (x && y && generator_constant) {
             normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87);
         } else {
-            normal = boost::none;
+            normal = {};
         }
     }
 
@@ -55,7 +55,7 @@ struct KeySlot {
 };
 
 std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots;
-std::array<boost::optional<AESKey>, 6> common_key_y_slots;
+std::array<std::optional<AESKey>, 6> common_key_y_slots;
 
 AESKey HexToKey(const std::string& hex) {
     if (hex.size() < 32) {
@@ -169,7 +169,7 @@ void SetNormalKey(std::size_t slot_id, const AESKey& key) {
 }
 
 bool IsNormalKeyAvailable(std::size_t slot_id) {
-    return key_slots.at(slot_id).normal.is_initialized();
+    return key_slots.at(slot_id).normal.has_value();
 }
 
 AESKey GetNormalKey(std::size_t slot_id) {
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 5b86fc28e..1689bc999 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -7,10 +7,10 @@
 #include <algorithm>
 #include <initializer_list>
 #include <memory>
+#include <optional>
 #include <string>
 #include <utility>
 #include <vector>
-#include <boost/optional.hpp>
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "core/file_sys/romfs_reader.h"
@@ -107,7 +107,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/loader/ncch.cpp b/src/core/loader/ncch.cpp
index 12301cb94..28e7639b3 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -49,11 +49,11 @@ FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) {
     return FileType::Error;
 }
 
-std::pair<boost::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() {
+std::pair<std::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() {
     if (!is_loaded) {
         ResultStatus res = base_ncch.Load();
         if (res != ResultStatus::Success) {
-            return std::make_pair(boost::none, res);
+            return std::make_pair(std::optional<u32>{}, res);
         }
     }
 
diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h
index 472bab80f..cc9ef1914 100644
--- a/src/core/loader/ncch.h
+++ b/src/core/loader/ncch.h
@@ -39,7 +39,7 @@ public:
      * Loads the Exheader and returns the system mode for this application.
      * @returns A pair with the optional system mode, and and the status.
      */
-    std::pair<boost::optional<u32>, ResultStatus> LoadKernelSystemMode() override;
+    std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() override;
 
     ResultStatus ReadCode(std::vector<u8>& buffer) override;
 
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 26967ad36..560b12a26 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -333,7 +333,7 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, bool cached) {
     PAddr paddr = start;
 
     for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) {
-        boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr);
+        std::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr);
         // While the physical <-> virtual mapping is 1:1 for the regions supported by the cache,
         // some games (like Pokemon Super Mystery Dungeon) will try to use textures that go beyond
         // the end address of VRAM, causing the Virtual->Physical translation to fail when flushing
@@ -433,7 +433,7 @@ void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) {
         VAddr overlap_start = std::max(start, region_start);
         VAddr overlap_end = std::min(end, region_end);
 
-        PAddr physical_start = TryVirtualToPhysicalAddress(overlap_start).value();
+        PAddr physical_start = *TryVirtualToPhysicalAddress(overlap_start);
         u32 overlap_size = overlap_end - overlap_start;
 
         auto* rasterizer = VideoCore::g_renderer->Rasterizer();
@@ -740,7 +740,7 @@ void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data)
     mmio_handler->Write64(addr, data);
 }
 
-boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
+std::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
     if (addr == 0) {
         return 0;
     } else if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) {
@@ -757,7 +757,7 @@ boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
         return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR;
     }
 
-    return boost::none;
+    return {};
 }
 
 PAddr VirtualToPhysicalAddress(const VAddr addr) {
@@ -770,7 +770,7 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) {
     return *paddr;
 }
 
-boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
+std::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
     if (addr == 0) {
         return 0;
     } else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) {
@@ -785,7 +785,7 @@ boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
         return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR;
     }
 
-    return boost::none;
+    return {};
 }
 
 } // namespace Memory
diff --git a/src/core/memory.h b/src/core/memory.h
index 73dbe091c..64a260899 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -6,9 +6,9 @@
 
 #include <array>
 #include <cstddef>
+#include <optional>
 #include <string>
 #include <vector>
-#include <boost/optional.hpp>
 #include "common/common_types.h"
 #include "core/mmio.h"
 
@@ -214,7 +214,7 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length);
  * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
  * address. This should be used by services to translate addresses for use by the hardware.
  */
-boost::optional<PAddr> TryVirtualToPhysicalAddress(VAddr addr);
+std::optional<PAddr> TryVirtualToPhysicalAddress(VAddr addr);
 
 /**
  * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
@@ -227,7 +227,7 @@ PAddr VirtualToPhysicalAddress(VAddr addr);
 /**
  * Undoes a mapping performed by VirtualToPhysicalAddress().
  */
-boost::optional<VAddr> PhysicalToVirtualAddress(PAddr paddr);
+std::optional<VAddr> PhysicalToVirtualAddress(PAddr paddr);
 
 /**
  * Gets a pointer to the memory region beginning at the specified physical address.
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
index a16ea34e4..f06157e3c 100644
--- a/src/input_common/udp/client.h
+++ b/src/input_common/udp/client.h
@@ -4,13 +4,14 @@
 
 #pragma once
 
+#include <functional>
 #include <memory>
 #include <mutex>
+#include <optional>
 #include <string>
 #include <thread>
 #include <tuple>
 #include <vector>
-#include <boost/optional.hpp>
 #include "common/common_types.h"
 #include "common/thread.h"
 #include "common/vector_math.h"
@@ -40,7 +41,7 @@ struct DeviceStatus {
         u16 max_x;
         u16 max_y;
     };
-    boost::optional<CalibrationData> touch_calibration;
+    std::optional<CalibrationData> touch_calibration;
 };
 
 class Client {
diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/udp/protocol.cpp
index ba3bb4c2f..d65069207 100644
--- a/src/input_common/udp/protocol.cpp
+++ b/src/input_common/udp/protocol.cpp
@@ -29,24 +29,24 @@ namespace Response {
  * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without
  * copying the buffer)
  */
-boost::optional<Type> Validate(u8* data, std::size_t size) {
+std::optional<Type> Validate(u8* data, std::size_t size) {
     if (size < sizeof(Header)) {
         LOG_DEBUG(Input, "Invalid UDP packet received");
-        return boost::none;
+        return {};
     }
     Header header;
     std::memcpy(&header, data, sizeof(Header));
     if (header.magic != SERVER_MAGIC) {
         LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
-        return boost::none;
+        return {};
     }
     if (header.protocol_version != PROTOCOL_VERSION) {
         LOG_ERROR(Input, "UDP Packet protocol mismatch");
-        return boost::none;
+        return {};
     }
     if (header.type < Type::Version || header.type > Type::PadData) {
         LOG_ERROR(Input, "UDP Packet is an unknown type");
-        return boost::none;
+        return {};
     }
 
     // Packet size must equal sizeof(Header) + sizeof(Data)
@@ -59,7 +59,7 @@ boost::optional<Type> Validate(u8* data, std::size_t size) {
             Input,
             "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
             size, header.payload_length, data_len + sizeof(Type));
-        return boost::none;
+        return {};
     }
 
     const u32 crc32 = header.crc;
@@ -70,7 +70,7 @@ boost::optional<Type> Validate(u8* data, std::size_t size) {
     result.process_bytes(data, data_len + sizeof(Header));
     if (crc32 != result.checksum()) {
         LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
-        return boost::none;
+        return {};
     }
     return header.type;
 }
diff --git a/src/input_common/udp/protocol.h b/src/input_common/udp/protocol.h
index ba1ad0562..1274b33b3 100644
--- a/src/input_common/udp/protocol.h
+++ b/src/input_common/udp/protocol.h
@@ -5,10 +5,10 @@
 #pragma once
 
 #include <array>
+#include <optional>
 #include <type_traits>
 #include <vector>
 #include <boost/crc.hpp>
-#include <boost/optional.hpp>
 #include "common/bit_field.h"
 #include "common/swap.h"
 
@@ -218,7 +218,7 @@ static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
  * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
  * copy the data into the appropriate struct for that Type
  */
-boost::optional<Type> Validate(u8* data, std::size_t size);
+std::optional<Type> Validate(u8* data, std::size_t size);
 
 } // namespace Response
 
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
index edb3fd5ad..53404114f 100644
--- a/src/input_common/udp/udp.cpp
+++ b/src/input_common/udp/udp.cpp
@@ -42,7 +42,7 @@ public:
     std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
         {
             std::lock_guard<std::mutex> guard(status->update_mutex);
-            status->touch_calibration.reset({});
+            status->touch_calibration.reset();
             // These default values work well for DS4 but probably not other touch inputs
             status->touch_calibration->min_x = params.Get("min_x", 100);
             status->touch_calibration->min_y = params.Get("min_y", 50);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 0de41043a..e0389d5b0 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -8,10 +8,10 @@
 #include <cstring>
 #include <iterator>
 #include <memory>
+#include <optional>
 #include <unordered_set>
 #include <utility>
 #include <vector>
-#include <boost/optional.hpp>
 #include <boost/range/iterator_range.hpp>
 #include <glad/glad.h>
 #include "common/alignment.h"
@@ -881,7 +881,7 @@ constexpr MatchFlags operator|(MatchFlags lhs, MatchFlags rhs) {
 template <MatchFlags find_flags>
 Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params,
                   ScaleMatch match_scale_type,
-                  boost::optional<SurfaceInterval> validate_interval = boost::none) {
+                  std::optional<SurfaceInterval> validate_interval = {}) {
     Surface match_surface = nullptr;
     bool match_valid = false;
     u32 match_scale = 0;
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 198188140..b04b08a4a 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -910,11 +910,11 @@ bool exec_shader();
 )";
 }
 
-boost::optional<std::string> DecompileProgram(const ProgramCode& program_code,
-                                              const SwizzleData& swizzle_data, u32 main_offset,
-                                              const RegGetter& inputreg_getter,
-                                              const RegGetter& outputreg_getter, bool sanitize_mul,
-                                              bool is_gs) {
+std::optional<std::string> DecompileProgram(const ProgramCode& program_code,
+                                            const SwizzleData& swizzle_data, u32 main_offset,
+                                            const RegGetter& inputreg_getter,
+                                            const RegGetter& outputreg_getter, bool sanitize_mul,
+                                            bool is_gs) {
 
     try {
         auto subroutines = ControlFlowAnalyzer(program_code, main_offset).MoveSubroutines();
@@ -923,7 +923,7 @@ boost::optional<std::string> DecompileProgram(const ProgramCode& program_code,
         return generator.MoveShaderCode();
     } catch (const DecompileFail& exception) {
         LOG_INFO(HW_GPU, "Shader decompilation failed: {}", exception.what());
-        return boost::none;
+        return {};
     }
 }
 
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index 91a71ce13..4dd8e73c3 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -4,8 +4,8 @@
 
 #include <array>
 #include <functional>
+#include <optional>
 #include <string>
-#include <boost/optional.hpp>
 #include "common/common_types.h"
 #include "video_core/shader/shader.h"
 
@@ -19,11 +19,11 @@ using RegGetter = std::function<std::string(u32)>;
 
 std::string GetCommonDeclarations();
 
-boost::optional<std::string> DecompileProgram(const ProgramCode& program_code,
-                                              const SwizzleData& swizzle_data, u32 main_offset,
-                                              const RegGetter& inputreg_getter,
-                                              const RegGetter& outputreg_getter, bool sanitize_mul,
-                                              bool is_gs);
+std::optional<std::string> DecompileProgram(const ProgramCode& program_code,
+                                            const SwizzleData& swizzle_data, u32 main_offset,
+                                            const RegGetter& inputreg_getter,
+                                            const RegGetter& outputreg_getter, bool sanitize_mul,
+                                            bool is_gs);
 
 } // namespace Decompiler
 } // namespace Shader
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 63de80305..ddf8583ea 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -1634,9 +1634,8 @@ void main() {
     return out;
 }
 
-boost::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup,
-                                                  const PicaVSConfig& config,
-                                                  bool separable_shader) {
+std::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup,
+                                                const PicaVSConfig& config, bool separable_shader) {
     std::string out = "#version 330 core\n";
     if (separable_shader) {
         out += "#extension GL_ARB_separate_shader_objects : enable\n";
@@ -1664,9 +1663,9 @@ boost::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetu
         get_output_reg, config.state.sanitize_mul, false);
 
     if (!program_source_opt)
-        return boost::none;
+        return {};
 
-    std::string& program_source = program_source_opt.get();
+    std::string& program_source = *program_source_opt;
 
     out += R"(
 #define uniforms vs_uniforms
@@ -1822,16 +1821,16 @@ void main() {
     return out;
 }
 
-boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup,
-                                                    const PicaGSConfig& config,
-                                                    bool separable_shader) {
+std::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup,
+                                                  const PicaGSConfig& config,
+                                                  bool separable_shader) {
     std::string out = "#version 330 core\n";
     if (separable_shader) {
         out += "#extension GL_ARB_separate_shader_objects : enable\n";
     }
 
     if (config.state.num_inputs % config.state.attributes_per_vertex != 0)
-        return boost::none;
+        return {};
 
     switch (config.state.num_inputs / config.state.attributes_per_vertex) {
     case 1:
@@ -1850,7 +1849,7 @@ boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSe
         out += "layout(triangles_adjacency) in;\n";
         break;
     default:
-        return boost::none;
+        return {};
     }
     out += "layout(triangle_strip, max_vertices = 30) out;\n\n";
 
@@ -1879,9 +1878,9 @@ boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSe
         get_output_reg, config.state.sanitize_mul, true);
 
     if (!program_source_opt)
-        return boost::none;
+        return {};
 
-    std::string& program_source = program_source_opt.get();
+    std::string& program_source = *program_source_opt;
 
     out += R"(
 Vertex output_buffer;
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index cc19a19bb..6f9252373 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -7,9 +7,9 @@
 #include <array>
 #include <cstring>
 #include <functional>
+#include <optional>
 #include <string>
 #include <type_traits>
-#include <boost/optional.hpp>
 #include "common/hash.h"
 #include "video_core/regs.h"
 #include "video_core/shader/shader.h"
@@ -228,9 +228,8 @@ std::string GenerateTrivialVertexShader(bool separable_shader);
  * Generates the GLSL vertex shader program source code for the given VS program
  * @returns String of the shader source code; boost::none on failure
  */
-boost::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup,
-                                                  const PicaVSConfig& config,
-                                                  bool separable_shader);
+std::optional<std::string> GenerateVertexShader(const Pica::Shader::ShaderSetup& setup,
+                                                const PicaVSConfig& config, bool separable_shader);
 
 /*
  * Generates the GLSL fixed geometry shader program source code for non-GS PICA pipeline
@@ -243,9 +242,9 @@ std::string GenerateFixedGeometryShader(const PicaFixedGSConfig& config, bool se
  * configuration
  * @returns String of the shader source code; boost::none on failure
  */
-boost::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup,
-                                                    const PicaGSConfig& config,
-                                                    bool separable_shader);
+std::optional<std::string> GenerateGeometryShader(const Pica::Shader::ShaderSetup& setup,
+                                                  const PicaGSConfig& config,
+                                                  bool separable_shader);
 
 /**
  * Generates the GLSL fragment shader program source code for the current Pica state
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 4593eab6a..0f6839ea8 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -162,8 +162,8 @@ private:
 // program buffer from the previous shader, which is hashed into the config, resulting several
 // different config values from the same shader program.
 template <typename KeyConfigType,
-          boost::optional<std::string> (*CodeGenerator)(const Pica::Shader::ShaderSetup&,
-                                                        const KeyConfigType&, bool),
+          std::optional<std::string> (*CodeGenerator)(const Pica::Shader::ShaderSetup&,
+                                                      const KeyConfigType&, bool),
           GLenum ShaderType>
 class ShaderDoubleCache {
 public:
@@ -177,7 +177,7 @@ public:
                 return 0;
             }
 
-            std::string& program = program_opt.get();
+            std::string& program = *program_opt;
             auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable});
             OGLShaderStage& cached_shader = iter->second;
             if (new_shader) {
diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp
index 639cd914e..a78bd28a5 100644
--- a/src/video_core/shader/shader_jit_x64_compiler.cpp
+++ b/src/video_core/shader/shader_jit_x64_compiler.cpp
@@ -754,7 +754,7 @@ void JitShader::Compile_LOOP(Instruction instr) {
     sub(LOOPCOUNT, 1);           // Increment loop count by 1
     jnz(l_loop_start);           // Loop if not equal
     L(*loop_break_label);
-    loop_break_label = boost::none;
+    loop_break_label.reset();
 
     looping = false;
 }
diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h
index 20266aa11..7a10844c7 100644
--- a/src/video_core/shader/shader_jit_x64_compiler.h
+++ b/src/video_core/shader/shader_jit_x64_compiler.h
@@ -6,9 +6,9 @@
 
 #include <array>
 #include <cstddef>
+#include <optional>
 #include <utility>
 #include <vector>
-#include <boost/optional.hpp>
 #include <nihstro/shader_bytecode.h>
 #include <xbyak.h>
 #include "common/bit_set.h"
@@ -123,7 +123,7 @@ private:
 
     /// Label pointing to the end of the current LOOP block. Used by the BREAKC instruction to break
     /// out of the loop.
-    boost::optional<Xbyak::Label> loop_break_label;
+    std::optional<Xbyak::Label> loop_break_label;
 
     /// Offsets in code where a return needs to be inserted
     std::vector<unsigned> return_offsets;