diff --git a/CMakeLists.txt b/CMakeLists.txt
index 93a477adf..e85534e8f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,9 @@
 # CMake 3.8 required for 17 to be a valid value for CXX_STANDARD
 cmake_minimum_required(VERSION 3.8)
+if (${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.15)
+    # Don't override the warning flags in MSVC:
+    cmake_policy(SET CMP0092 NEW)
+endif ()
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
 list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
 include(DownloadExternals)
@@ -35,6 +39,8 @@ CMAKE_DEPENDENT_OPTION(ENABLE_MF "Use Media Foundation decoder (preferred over F
 
 CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ON "MINGW" OFF)
 
+option(USE_SYSTEM_BOOST "Use the system Boost libs (instead of the bundled ones)" OFF)
+
 CMAKE_DEPENDENT_OPTION(ENABLE_FDK "Use FDK AAC decoder" OFF "NOT ENABLE_FFMPEG_AUDIO_DECODER;NOT ENABLE_MF" OFF)
 
 if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
@@ -126,16 +132,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
 # System imported libraries
 # ======================
 
-find_package(Boost 1.66.0 QUIET)
-if (NOT Boost_FOUND)
-    message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
-
-    set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
-    set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost")
-    set(Boost_NO_SYSTEM_PATHS OFF)
-    find_package(Boost QUIET REQUIRED)
-endif()
-
 # Prefer the -pthread flag on Linux.
 set(THREADS_PREFER_PTHREAD_FLAG ON)
 find_package(Threads REQUIRED)
@@ -339,8 +335,21 @@ git_describe(GIT_DESC --always --long --dirty)
 git_branch_name(GIT_BRANCH)
 get_timestamp(BUILD_DATE)
 
+if (NOT USE_SYSTEM_BOOST)
+    add_definitions( -DBOOST_ALL_NO_LIB )
+endif()
+
 enable_testing()
 add_subdirectory(externals)
+
+# Boost
+if (USE_SYSTEM_BOOST)
+    find_package(Boost 1.70.0 QUIET REQUIRED)
+else()
+    add_library(Boost::boost ALIAS boost)
+    add_library(Boost::serialization ALIAS boost_serialization)
+endif()
+
 add_subdirectory(src)
 add_subdirectory(dist/installer)
 
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 2aeaa5688..7d687a4dc 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -1,9 +1,28 @@
 # Definitions for all external bundled libraries
 
+# Suppress warnings from external libraries
+if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
+    add_compile_options(/W0)
+endif()
+
 set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
 include(DownloadExternals)
 include(ExternalProject)
 
+# Boost
+set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost")
+set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/boost")
+set(Boost_NO_SYSTEM_PATHS ON)
+add_library(boost INTERFACE)
+target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
+
+# Boost::serialization
+file(GLOB boost_serialization_SRC "${CMAKE_SOURCE_DIR}/externals/boost/libs/serialization/src/*.cpp")
+add_library(boost_serialization STATIC ${boost_serialization_SRC})
+target_link_libraries(boost_serialization PUBLIC boost)
+
+# Add additional boost libs here; remember to ALIAS them in the root CMakeLists!
+
 # Catch
 add_library(catch-single-include INTERFACE)
 target_include_directories(catch-single-include INTERFACE catch/single_include)
diff --git a/externals/boost b/externals/boost
index 502437b2a..36603a1e6 160000
--- a/externals/boost
+++ b/externals/boost
@@ -1 +1 @@
-Subproject commit 502437b2ae3f1da821aa7d5d5174ec356fa89269
+Subproject commit 36603a1e665e849d29b1735a12c0a51284a10dd0
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 33d7400fa..1ada1b3eb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -31,6 +31,7 @@ if (MSVC)
     # /Zc:externConstexpr - Allow extern constexpr variables to have external linkage, like the standard mandates
     # /Zc:inline          - Let codegen omit inline functions in object files
     # /Zc:throwingNew     - Let codegen assume `operator new` (without std::nothrow) will never return null
+    # /external:*         - Suppress warnings from external headers
     add_compile_options(
         /W3
         /MP
@@ -42,6 +43,10 @@ if (MSVC)
         /Zc:externConstexpr
         /Zc:inline
         /Zc:throwingNew
+        /experimental:external
+        /external:I "${CMAKE_SOURCE_DIR}/externals"
+        /external:anglebrackets
+        /external:W0
     )
 
     # /GS- - No stack buffer overflow checks
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt
index 3cb9b538d..63a566596 100644
--- a/src/audio_core/CMakeLists.txt
+++ b/src/audio_core/CMakeLists.txt
@@ -35,7 +35,7 @@ add_library(audio_core STATIC
 
 create_target_directory_groups(audio_core)
 
-target_link_libraries(audio_core PUBLIC common core)
+target_link_libraries(audio_core PUBLIC common)
 target_link_libraries(audio_core PRIVATE SoundTouch teakra)
 
 if(ENABLE_MF)
diff --git a/src/audio_core/dsp_interface.h b/src/audio_core/dsp_interface.h
index 5b83e684d..41ab0c0dc 100644
--- a/src/audio_core/dsp_interface.h
+++ b/src/audio_core/dsp_interface.h
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <vector>
+#include <boost/serialization/access.hpp>
 #include "audio_core/audio_types.h"
 #include "audio_core/time_stretch.h"
 #include "common/common_types.h"
@@ -113,6 +114,10 @@ private:
     Common::RingBuffer<s16, 0x2000, 2> fifo;
     std::array<s16, 2> last_frame{};
     TimeStretcher time_stretcher;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {}
+    friend class boost::serialization::access;
 };
 
 } // namespace AudioCore
diff --git a/src/audio_core/hle/hle.cpp b/src/audio_core/hle/hle.cpp
index 1725fdf78..df52f461b 100644
--- a/src/audio_core/hle/hle.cpp
+++ b/src/audio_core/hle/hle.cpp
@@ -2,6 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/vector.hpp>
+#include <boost/serialization/weak_ptr.hpp>
 #include "audio_core/audio_types.h"
 #ifdef HAVE_MF
 #include "audio_core/hle/wmf_decoder.h"
@@ -26,11 +31,22 @@
 #include "core/core.h"
 #include "core/core_timing.h"
 
+SERIALIZE_EXPORT_IMPL(AudioCore::DspHle)
+
 using InterruptType = Service::DSP::DSP_DSP::InterruptType;
 using Service::DSP::DSP_DSP;
 
 namespace AudioCore {
 
+DspHle::DspHle() : DspHle(Core::System::GetInstance().Memory()) {}
+
+template <class Archive>
+void DspHle::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<DspInterface>(*this);
+    ar&* impl.get();
+}
+SERIALIZE_IMPL(DspHle)
+
 static constexpr u64 audio_frame_ticks = 1310252ull; ///< Units: ARM11 cycles
 
 struct DspHle::Impl final {
@@ -64,7 +80,7 @@ private:
     void AudioTickCallback(s64 cycles_late);
 
     DspState dsp_state = DspState::Off;
-    std::array<std::vector<u8>, num_dsp_pipe> pipe_data;
+    std::array<std::vector<u8>, num_dsp_pipe> pipe_data{};
 
     HLE::DspMemory dsp_memory;
     std::array<HLE::Source, HLE::num_sources> sources{{
@@ -74,14 +90,25 @@ private:
         HLE::Source(15), HLE::Source(16), HLE::Source(17), HLE::Source(18), HLE::Source(19),
         HLE::Source(20), HLE::Source(21), HLE::Source(22), HLE::Source(23),
     }};
-    HLE::Mixers mixers;
+    HLE::Mixers mixers{};
 
     DspHle& parent;
-    Core::TimingEventType* tick_event;
+    Core::TimingEventType* tick_event{};
 
-    std::unique_ptr<HLE::DecoderBase> decoder;
+    std::unique_ptr<HLE::DecoderBase> decoder{};
 
-    std::weak_ptr<DSP_DSP> dsp_dsp;
+    std::weak_ptr<DSP_DSP> dsp_dsp{};
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& dsp_state;
+        ar& pipe_data;
+        ar& dsp_memory.raw_memory;
+        ar& sources;
+        ar& mixers;
+        ar& dsp_dsp;
+    }
+    friend class boost::serialization::access;
 };
 
 DspHle::Impl::Impl(DspHle& parent_, Memory::MemorySystem& memory) : parent(parent_) {
diff --git a/src/audio_core/hle/hle.h b/src/audio_core/hle/hle.h
index 4ab468331..11ec2820a 100644
--- a/src/audio_core/hle/hle.h
+++ b/src/audio_core/hle/hle.h
@@ -7,6 +7,7 @@
 #include <array>
 #include <memory>
 #include <vector>
+#include <boost/serialization/export.hpp>
 #include "audio_core/audio_types.h"
 #include "audio_core/dsp_interface.h"
 #include "common/common_types.h"
@@ -42,6 +43,14 @@ private:
     struct Impl;
     friend struct Impl;
     std::unique_ptr<Impl> impl;
+
+    DspHle();
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 } // namespace AudioCore
+
+BOOST_CLASS_EXPORT_KEY(AudioCore::DspHle)
diff --git a/src/audio_core/hle/mixers.h b/src/audio_core/hle/mixers.h
index c5bfd512f..5043be38c 100644
--- a/src/audio_core/hle/mixers.h
+++ b/src/audio_core/hle/mixers.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <array>
+#include <boost/serialization/array.hpp>
 #include "audio_core/audio_types.h"
 #include "audio_core/hle/shared_memory.h"
 
@@ -54,6 +55,17 @@ private:
     void DownmixAndMixIntoCurrentFrame(float gain, const QuadFrame32& samples);
     /// INTERNAL: Generate DspStatus based on internal state.
     DspStatus GetCurrentStatus() const;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& current_frame;
+        ar& state.intermediate_mixer_volume;
+        ar& state.mixer1_enabled;
+        ar& state.mixer2_enabled;
+        ar& state.intermediate_mix_buffer;
+        ar& state.output_format;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace AudioCore::HLE
diff --git a/src/audio_core/hle/shared_memory.h b/src/audio_core/hle/shared_memory.h
index 8ab9aa88e..43bf1df69 100644
--- a/src/audio_core/hle/shared_memory.h
+++ b/src/audio_core/hle/shared_memory.h
@@ -8,6 +8,7 @@
 #include <cstddef>
 #include <memory>
 #include <type_traits>
+#include <boost/serialization/access.hpp>
 #include "audio_core/audio_types.h"
 #include "audio_core/hle/common.h"
 #include "common/bit_field.h"
@@ -56,6 +57,12 @@ private:
         return (value << 16) | (value >> 16);
     }
     u32_le storage;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& storage;
+    }
+    friend class boost::serialization::access;
 };
 static_assert(std::is_trivially_copyable<u32_dsp>::value, "u32_dsp isn't trivially copyable");
 
diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h
index b0db4a5d3..17c31672b 100644
--- a/src/audio_core/hle/source.h
+++ b/src/audio_core/hle/source.h
@@ -6,6 +6,10 @@
 
 #include <array>
 #include <vector>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/deque.hpp>
+#include <boost/serialization/priority_queue.hpp>
+#include <boost/serialization/vector.hpp>
 #include <queue>
 #include "audio_core/audio_types.h"
 #include "audio_core/codec.h"
@@ -85,6 +89,24 @@ private:
         bool from_queue;
         u32_dsp play_position; // = 0;
         bool has_played;       // = false;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& physical_address;
+            ar& length;
+            ar& adpcm_ps;
+            ar& adpcm_yn;
+            ar& adpcm_dirty;
+            ar& is_looping;
+            ar& buffer_id;
+            ar& mono_or_stereo;
+            ar& format;
+            ar& from_queue;
+            ar& play_position;
+            ar& has_played;
+        }
+        friend class boost::serialization::access;
     };
 
     struct BufferOrder {
@@ -107,7 +129,7 @@ private:
 
         // Buffer queue
 
-        std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder> input_queue;
+        std::priority_queue<Buffer, std::vector<Buffer>, BufferOrder> input_queue = {};
         MonoOrStereo mono_or_stereo = MonoOrStereo::Mono;
         Format format = Format::ADPCM;
 
@@ -115,7 +137,7 @@ private:
 
         u32 current_sample_number = 0;
         u32 next_sample_number = 0;
-        AudioInterp::StereoBuffer16 current_buffer;
+        AudioInterp::StereoBuffer16 current_buffer = {};
 
         // buffer_id state
 
@@ -135,7 +157,27 @@ private:
 
         // Filter state
 
-        SourceFilters filters;
+        SourceFilters filters = {};
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& enabled;
+            ar& sync;
+            ar& gain;
+            ar& input_queue;
+            ar& mono_or_stereo;
+            ar& format;
+            ar& current_sample_number;
+            ar& next_sample_number;
+            ar& current_buffer;
+            ar& buffer_update;
+            ar& current_buffer_id;
+            ar& adpcm_coeffs;
+            ar& rate_multiplier;
+            ar& interpolation_mode;
+        }
+        friend class boost::serialization::access;
 
     } state;
 
@@ -150,6 +192,12 @@ private:
     bool DequeueBuffer();
     /// INTERNAL: Generates a SourceStatus::Status based on our internal state.
     SourceStatus::Status GetCurrentStatus();
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& state;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace AudioCore::HLE
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index ddabad13b..053d88f51 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -132,7 +132,9 @@ void OpenGLWindow::Present() {
         return;
 
     context->makeCurrent(this);
-    VideoCore::g_renderer->TryPresent(100);
+    if (VideoCore::g_renderer) {
+        VideoCore::g_renderer->TryPresent(100);
+    }
     context->swapBuffers(this);
     auto f = context->versionFunctions<QOpenGLFunctions_3_3_Core>();
     f->glFinish();
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 9830f9000..88be0330f 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -57,7 +57,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
 // This must be in alphabetical order according to action name as it must have the same order as
 // UISetting::values.shortcuts, which is alphabetically ordered.
 // clang-format off
-const std::array<UISettings::Shortcut, 21> default_hotkeys{
+const std::array<UISettings::Shortcut, 23> default_hotkeys{
     {{QStringLiteral("Advance Frame"),            QStringLiteral("Main Window"), {QStringLiteral("\\"), Qt::ApplicationShortcut}},
      {QStringLiteral("Capture Screenshot"),       QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
      {QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
@@ -73,6 +73,8 @@ const std::array<UISettings::Shortcut, 21> default_hotkeys{
      {QStringLiteral("Rotate Screens Upright"),   QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
      {QStringLiteral("Stop Emulation"),           QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
      {QStringLiteral("Swap Screens"),             QStringLiteral("Main Window"), {QStringLiteral("F9"), Qt::WindowShortcut}},
+     {QStringLiteral("Save to Oldest Slot"),      QStringLiteral("Main Window"), {QStringLiteral("Ctrl+C"), Qt::WindowShortcut}},
+     {QStringLiteral("Load from Newest Slot"),    QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
      {QStringLiteral("Toggle Filter Bar"),        QStringLiteral("Main Window"), {QStringLiteral("Ctrl+F"), Qt::WindowShortcut}},
      {QStringLiteral("Toggle Frame Advancing"),   QStringLiteral("Main Window"), {QStringLiteral("Ctrl+A"), Qt::ApplicationShortcut}},
      {QStringLiteral("Toggle Screen Layout"),     QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::WindowShortcut}},
diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp
index 540ab6c41..32ee7a1df 100644
--- a/src/citra_qt/debugger/wait_tree.cpp
+++ b/src/citra_qt/debugger/wait_tree.cpp
@@ -216,7 +216,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
     std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
 
     const auto& thread = static_cast<const Kernel::Thread&>(object);
-    const auto* process = thread.owner_process;
+    const auto& process = thread.owner_process;
 
     QString processor;
     switch (thread.processor_id) {
@@ -237,6 +237,7 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
         break;
     }
 
+    list.push_back(std::make_unique<WaitTreeText>(tr("object id = %1").arg(thread.GetObjectId())));
     list.push_back(std::make_unique<WaitTreeText>(tr("processor = %1").arg(processor)));
     list.push_back(std::make_unique<WaitTreeText>(tr("thread id = %1").arg(thread.GetThreadId())));
     list.push_back(
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 1f2a71286..7389b8c23 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <clocale>
+#include <fstream>
 #include <memory>
 #include <thread>
 #include <QDesktopWidget>
@@ -78,6 +79,7 @@
 #include "core/hle/service/nfc/nfc.h"
 #include "core/loader/loader.h"
 #include "core/movie.h"
+#include "core/savestate.h"
 #include "core/settings.h"
 #include "game_list_p.h"
 #include "video_core/renderer_base.h"
@@ -171,6 +173,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
     InitializeWidgets();
     InitializeDebugWidgets();
     InitializeRecentFileMenuActions();
+    InitializeSaveStateMenuActions();
     InitializeHotkeys();
     ShowUpdaterWidgets();
 
@@ -396,6 +399,32 @@ void GMainWindow::InitializeRecentFileMenuActions() {
     UpdateRecentFiles();
 }
 
+void GMainWindow::InitializeSaveStateMenuActions() {
+    for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
+        actions_load_state[i] = new QAction(this);
+        actions_load_state[i]->setData(i + 1);
+        connect(actions_load_state[i], &QAction::triggered, this, &GMainWindow::OnLoadState);
+        ui.menu_Load_State->addAction(actions_load_state[i]);
+
+        actions_save_state[i] = new QAction(this);
+        actions_save_state[i]->setData(i + 1);
+        connect(actions_save_state[i], &QAction::triggered, this, &GMainWindow::OnSaveState);
+        ui.menu_Save_State->addAction(actions_save_state[i]);
+    }
+
+    connect(ui.action_Load_from_Newest_Slot, &QAction::triggered,
+            [this] { actions_load_state[newest_slot - 1]->trigger(); });
+    connect(ui.action_Save_to_Oldest_Slot, &QAction::triggered,
+            [this] { actions_save_state[oldest_slot - 1]->trigger(); });
+
+    connect(ui.menu_Load_State->menuAction(), &QAction::hovered, this,
+            &GMainWindow::UpdateSaveStates);
+    connect(ui.menu_Save_State->menuAction(), &QAction::hovered, this,
+            &GMainWindow::UpdateSaveStates);
+
+    UpdateSaveStates();
+}
+
 void GMainWindow::InitializeHotkeys() {
     hotkey_registry.LoadHotkeys();
 
@@ -509,6 +538,10 @@ void GMainWindow::InitializeHotkeys() {
                     OnCaptureScreenshot();
                 }
             });
+    connect(hotkey_registry.GetHotkey(main_window, ui.action_Load_from_Newest_Slot->text(), this),
+            &QShortcut::activated, ui.action_Load_from_Newest_Slot, &QAction::trigger);
+    connect(hotkey_registry.GetHotkey(main_window, ui.action_Save_to_Oldest_Slot->text(), this),
+            &QShortcut::activated, ui.action_Save_to_Oldest_Slot, &QAction::trigger);
 }
 
 void GMainWindow::ShowUpdaterWidgets() {
@@ -687,6 +720,7 @@ void GMainWindow::ConnectMenuEvents() {
         if (emulation_running) {
             ui.action_Enable_Frame_Advancing->setChecked(true);
             ui.action_Advance_Frame->setEnabled(true);
+            Core::System::GetInstance().frame_limiter.SetFrameAdvancing(true);
             Core::System::GetInstance().frame_limiter.AdvanceFrame();
         }
     });
@@ -1091,6 +1125,8 @@ void GMainWindow::ShutdownGame() {
     game_fps_label->setVisible(false);
     emu_frametime_label->setVisible(false);
 
+    UpdateSaveStates();
+
     emulation_running = false;
 
     if (defer_update_prompt) {
@@ -1137,6 +1173,62 @@ void GMainWindow::UpdateRecentFiles() {
     ui.menu_recent_files->setEnabled(num_recent_files != 0);
 }
 
+void GMainWindow::UpdateSaveStates() {
+    if (!Core::System::GetInstance().IsPoweredOn()) {
+        ui.menu_Load_State->setEnabled(false);
+        ui.menu_Save_State->setEnabled(false);
+        return;
+    }
+
+    ui.menu_Load_State->setEnabled(true);
+    ui.menu_Save_State->setEnabled(true);
+    ui.action_Load_from_Newest_Slot->setEnabled(false);
+
+    oldest_slot = newest_slot = 0;
+    oldest_slot_time = std::numeric_limits<u64>::max();
+    newest_slot_time = 0;
+
+    u64 title_id;
+    if (Core::System::GetInstance().GetAppLoader().ReadProgramId(title_id) !=
+        Loader::ResultStatus::Success) {
+        return;
+    }
+    auto savestates = Core::ListSaveStates(title_id);
+    for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
+        actions_load_state[i]->setEnabled(false);
+        actions_load_state[i]->setText(tr("Slot %1").arg(i + 1));
+        actions_save_state[i]->setText(tr("Slot %1").arg(i + 1));
+    }
+    for (const auto& savestate : savestates) {
+        const auto text = tr("Slot %1 - %2")
+                              .arg(savestate.slot)
+                              .arg(QDateTime::fromSecsSinceEpoch(savestate.time)
+                                       .toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")));
+        actions_load_state[savestate.slot - 1]->setEnabled(true);
+        actions_load_state[savestate.slot - 1]->setText(text);
+        actions_save_state[savestate.slot - 1]->setText(text);
+
+        ui.action_Load_from_Newest_Slot->setEnabled(true);
+
+        if (savestate.time > newest_slot_time) {
+            newest_slot = savestate.slot;
+            newest_slot_time = savestate.time;
+        }
+        if (savestate.time < oldest_slot_time) {
+            oldest_slot = savestate.slot;
+            oldest_slot_time = savestate.time;
+        }
+    }
+    for (u32 i = 0; i < Core::SaveStateSlotCount; ++i) {
+        if (!actions_load_state[i]->isEnabled()) {
+            // Prefer empty slot
+            oldest_slot = i + 1;
+            oldest_slot_time = 0;
+            break;
+        }
+    }
+}
+
 void GMainWindow::OnGameListLoadFile(QString game_path) {
     BootGame(game_path);
 }
@@ -1385,7 +1477,7 @@ void GMainWindow::OnCIAInstallFinished() {
 
 void GMainWindow::OnMenuRecentFile() {
     QAction* action = qobject_cast<QAction*>(sender());
-    assert(action);
+    ASSERT(action);
 
     const QString filename = action->data().toString();
     if (QFileInfo::exists(filename)) {
@@ -1429,6 +1521,8 @@ void GMainWindow::OnStartGame() {
     ui.action_Capture_Screenshot->setEnabled(true);
 
     discord_rpc->Update();
+
+    UpdateSaveStates();
 }
 
 void GMainWindow::OnPauseGame() {
@@ -1576,6 +1670,23 @@ void GMainWindow::OnCheats() {
     cheat_dialog.exec();
 }
 
+void GMainWindow::OnSaveState() {
+    QAction* action = qobject_cast<QAction*>(sender());
+    assert(action);
+
+    Core::System::GetInstance().SendSignal(Core::System::Signal::Save, action->data().toUInt());
+    Core::System::GetInstance().frame_limiter.AdvanceFrame();
+    newest_slot = action->data().toUInt();
+}
+
+void GMainWindow::OnLoadState() {
+    QAction* action = qobject_cast<QAction*>(sender());
+    assert(action);
+
+    Core::System::GetInstance().SendSignal(Core::System::Signal::Load, action->data().toUInt());
+    Core::System::GetInstance().frame_limiter.AdvanceFrame();
+}
+
 void GMainWindow::OnConfigure() {
     ConfigureDialog configureDialog(this, hotkey_registry,
                                     !multiplayer_state->IsHostingPublicRoom());
@@ -1968,6 +2079,9 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
 
         title = tr("System Archive Not Found");
         status_message = tr("System Archive Missing");
+    } else if (result == Core::System::ResultStatus::ErrorSavestate) {
+        title = tr("Save/load Error");
+        message = QString::fromStdString(details);
     } else {
         title = tr("Fatal Error");
         message =
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 4bdbd622f..234b9893a 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <array>
 #include <memory>
 #include <QLabel>
 #include <QMainWindow>
@@ -14,6 +15,7 @@
 #include "common/announce_multiplayer_room.h"
 #include "core/core.h"
 #include "core/hle/service/am/am.h"
+#include "core/savestate.h"
 #include "ui_main.h"
 
 class AboutDialog;
@@ -106,6 +108,7 @@ private:
     void InitializeWidgets();
     void InitializeDebugWidgets();
     void InitializeRecentFileMenuActions();
+    void InitializeSaveStateMenuActions();
 
     void SetDefaultUIGeometry();
     void SyncMenuUISettings();
@@ -149,6 +152,8 @@ private:
      */
     void UpdateRecentFiles();
 
+    void UpdateSaveStates();
+
     /**
      * If the emulation is running,
      * asks the user if he really want to close the emulator
@@ -163,6 +168,8 @@ private slots:
     void OnStartGame();
     void OnPauseGame();
     void OnStopGame();
+    void OnSaveState();
+    void OnLoadState();
     void OnMenuReportCompatibility();
     /// Called whenever a user selects a game in the game list widget.
     void OnGameListLoadFile(QString game_path);
@@ -282,6 +289,13 @@ private:
     bool defer_update_prompt = false;
 
     QAction* actions_recent_files[max_recent_files_item];
+    std::array<QAction*, Core::SaveStateSlotCount> actions_load_state;
+    std::array<QAction*, Core::SaveStateSlotCount> actions_save_state;
+
+    u32 oldest_slot;
+    u64 oldest_slot_time;
+    u32 newest_slot;
+    u64 newest_slot_time;
 
     QTranslator translator;
 
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui
index 6011b0e7f..2eff98083 100644
--- a/src/citra_qt/main.ui
+++ b/src/citra_qt/main.ui
@@ -79,11 +79,28 @@
     <property name="title">
      <string>&amp;Emulation</string>
     </property>
+    <widget class="QMenu" name="menu_Save_State">
+     <property name="title">
+      <string>Save State</string>
+     </property>
+     <addaction name="action_Save_to_Oldest_Slot"/>
+     <addaction name="separator"/>
+    </widget>
+    <widget class="QMenu" name="menu_Load_State">
+     <property name="title">
+      <string>Load State</string>
+     </property>
+     <addaction name="action_Load_from_Newest_Slot"/>
+     <addaction name="separator"/>
+    </widget>
     <addaction name="action_Start"/>
     <addaction name="action_Pause"/>
     <addaction name="action_Stop"/>
     <addaction name="action_Restart"/>
     <addaction name="separator"/>
+    <addaction name="menu_Load_State"/>
+    <addaction name="menu_Save_State"/>
+    <addaction name="separator"/>
     <addaction name="action_Report_Compatibility"/>
     <addaction name="separator"/>
     <addaction name="action_Configure"/>
@@ -217,6 +234,22 @@
     <string>&amp;Stop</string>
    </property>
   </action>
+  <action name="action_Save">
+    <property name="enabled">
+      <bool>false</bool>
+    </property>
+    <property name="text">
+      <string>Save</string>
+    </property>
+  </action>
+  <action name="action_Load">
+    <property name="enabled">
+      <bool>false</bool>
+    </property>
+    <property name="text">
+      <string>Load</string>
+    </property>
+  </action>
   <action name="action_FAQ">
    <property name="text">
     <string>FAQ</string>
@@ -235,6 +268,16 @@
     <string>Single Window Mode</string>
    </property>
   </action>
+  <action name="action_Save_to_Oldest_Slot">
+   <property name="text">
+    <string>Save to Oldest Slot</string>
+   </property>
+  </action>
+  <action name="action_Load_from_Newest_Slot">
+   <property name="text">
+    <string>Load from Newest Slot</string>
+   </property>
+  </action>
   <action name="action_Configure">
    <property name="text">
     <string>Configure...</string>
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index af07ac215..7ab54242d 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -54,6 +54,7 @@ add_custom_command(OUTPUT scm_rev.cpp
 add_library(common STATIC
     alignment.h
     announce_multiplayer_room.h
+    archives.h
     assert.h
     detached_tasks.cpp
     detached_tasks.h
@@ -66,6 +67,7 @@ add_library(common STATIC
     common_funcs.h
     common_paths.h
     common_types.h
+    construct.h
     file_util.cpp
     file_util.h
     hash.h
@@ -78,6 +80,8 @@ add_library(common STATIC
     logging/text_formatter.cpp
     logging/text_formatter.h
     math_util.h
+    memory_ref.h
+    memory_ref.cpp
     microprofile.cpp
     microprofile.h
     microprofileui.h
@@ -89,6 +93,11 @@ add_library(common STATIC
     scm_rev.cpp
     scm_rev.h
     scope_exit.h
+    serialization/atomic.h
+    serialization/boost_discrete_interval.hpp
+    serialization/boost_flat_set.h
+    serialization/boost_small_vector.hpp
+    serialization/boost_vector.hpp
     string_util.cpp
     string_util.h
     swap.h
@@ -121,7 +130,7 @@ endif()
 
 create_target_directory_groups(common)
 
-target_link_libraries(common PUBLIC fmt microprofile)
+target_link_libraries(common PUBLIC fmt microprofile Boost::boost Boost::serialization)
 target_link_libraries(common PRIVATE libzstd_static)
 if (ARCHITECTURE_x86_64)
     target_link_libraries(common PRIVATE xbyak)
diff --git a/src/common/archives.h b/src/common/archives.h
new file mode 100644
index 000000000..b9f4330bd
--- /dev/null
+++ b/src/common/archives.h
@@ -0,0 +1,21 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <boost/archive/binary_iarchive.hpp>
+#include <boost/archive/binary_oarchive.hpp>
+#include <boost/serialization/export.hpp>
+
+using iarchive = boost::archive::binary_iarchive;
+using oarchive = boost::archive::binary_oarchive;
+
+#define SERIALIZE_IMPL(A)                                                                          \
+    template void A::serialize<iarchive>(iarchive & ar, const unsigned int file_version);          \
+    template void A::serialize<oarchive>(oarchive & ar, const unsigned int file_version);
+
+#define SERIALIZE_EXPORT_IMPL(A)                                                                   \
+    BOOST_CLASS_EXPORT_IMPLEMENT(A)                                                                \
+    BOOST_SERIALIZATION_REGISTER_ARCHIVE(iarchive)                                                 \
+    BOOST_SERIALIZATION_REGISTER_ARCHIVE(oarchive)
diff --git a/src/common/common_paths.h b/src/common/common_paths.h
index 13e71615e..eec4dde9c 100644
--- a/src/common/common_paths.h
+++ b/src/common/common_paths.h
@@ -47,6 +47,7 @@
 #define DUMP_DIR "dump"
 #define LOAD_DIR "load"
 #define SHADER_DIR "shaders"
+#define STATES_DIR "states"
 
 // Filenames
 // Files in the directory returned by GetUserPath(UserPath::LogDir)
diff --git a/src/common/construct.h b/src/common/construct.h
new file mode 100644
index 000000000..cb47bb46e
--- /dev/null
+++ b/src/common/construct.h
@@ -0,0 +1,34 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <boost/serialization/serialization.hpp>
+
+/// Allows classes to define `save_construct` and `load_construct` methods for serialization
+/// This is used where we don't call the default constructor during deserialization, as a shortcut
+/// instead of using load_construct_data directly
+class construct_access {
+public:
+    template <class Archive, class T>
+    static void save_construct(Archive& ar, const T* t, const unsigned int file_version) {
+        t->save_construct(ar, file_version);
+    }
+    template <class Archive, class T>
+    static void load_construct(Archive& ar, T* t, const unsigned int file_version) {
+        T::load_construct(ar, t, file_version);
+    }
+};
+
+#define BOOST_SERIALIZATION_CONSTRUCT(T)                                                           \
+    namespace boost::serialization {                                                               \
+    template <class Archive>                                                                       \
+    void save_construct_data(Archive& ar, const T* t, const unsigned int file_version) {           \
+        construct_access::save_construct(ar, t, file_version);                                     \
+    }                                                                                              \
+    template <class Archive>                                                                       \
+    void load_construct_data(Archive& ar, T* t, const unsigned int file_version) {                 \
+        construct_access::load_construct(ar, t, file_version);                                     \
+    }                                                                                              \
+    }
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index ce41890f7..fd1a3fd30 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -726,6 +726,34 @@ void SetUserPath(const std::string& path) {
     g_paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP);
     g_paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
     g_paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
+    g_paths.emplace(UserPath::StatesDir, user_path + STATES_DIR DIR_SEP);
+}
+
+std::string g_currentRomPath{};
+
+void SetCurrentRomPath(const std::string& path) {
+    g_currentRomPath = path;
+}
+
+bool StringReplace(std::string& haystack, const std::string& a, const std::string& b, bool swap) {
+    const auto& needle = swap ? b : a;
+    const auto& replacement = swap ? a : b;
+    if (needle.empty()) {
+        return false;
+    }
+    auto index = haystack.find(needle, 0);
+    if (index == std::string::npos) {
+        return false;
+    }
+    haystack.replace(index, needle.size(), replacement);
+    return true;
+}
+
+std::string SerializePath(const std::string& input, bool is_saving) {
+    auto result = input;
+    StringReplace(result, "%CITRA_ROM_FILE%", g_currentRomPath, is_saving);
+    StringReplace(result, "%CITRA_USER_DIR%", GetUserPath(UserPath::UserDir), is_saving);
+    return result;
 }
 
 const std::string& GetUserPath(UserPath path) {
@@ -882,8 +910,9 @@ std::string SanitizePath(std::string_view path_, DirectorySeparator directory_se
 
 IOFile::IOFile() {}
 
-IOFile::IOFile(const std::string& filename, const char openmode[], int flags) {
-    Open(filename, openmode, flags);
+IOFile::IOFile(const std::string& filename, const char openmode[], int flags)
+    : filename(filename), openmode(openmode), flags(flags) {
+    Open();
 }
 
 IOFile::~IOFile() {
@@ -902,10 +931,14 @@ IOFile& IOFile::operator=(IOFile&& other) noexcept {
 void IOFile::Swap(IOFile& other) noexcept {
     std::swap(m_file, other.m_file);
     std::swap(m_good, other.m_good);
+    std::swap(filename, other.filename);
+    std::swap(openmode, other.openmode);
+    std::swap(flags, other.flags);
 }
 
-bool IOFile::Open(const std::string& filename, const char openmode[], int flags) {
+bool IOFile::Open() {
     Close();
+
 #ifdef _WIN32
     if (flags != 0) {
         m_file = _wfsopen(Common::UTF8ToUTF16W(filename).c_str(),
@@ -916,7 +949,7 @@ bool IOFile::Open(const std::string& filename, const char openmode[], int flags)
                            Common::UTF8ToUTF16W(openmode).c_str()) == 0;
     }
 #else
-    m_file = std::fopen(filename.c_str(), openmode);
+    m_file = std::fopen(filename.c_str(), openmode.c_str());
     m_good = m_file != nullptr;
 #endif
 
diff --git a/src/common/file_util.h b/src/common/file_util.h
index 2037db13e..d5915b8b7 100644
--- a/src/common/file_util.h
+++ b/src/common/file_util.h
@@ -14,6 +14,9 @@
 #include <string_view>
 #include <type_traits>
 #include <vector>
+#include <boost/serialization/split_member.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/wrapper.hpp>
 #include "common/common_types.h"
 #ifdef _MSC_VER
 #include "common/string_util.h"
@@ -34,10 +37,39 @@ enum class UserPath {
     RootDir,
     SDMCDir,
     ShaderDir,
+    StatesDir,
     SysDataDir,
     UserDir,
 };
 
+// Replaces install-specific paths with standard placeholders, and back again
+std::string SerializePath(const std::string& input, bool is_saving);
+
+// A serializable path string
+struct Path : public boost::serialization::wrapper_traits<const Path> {
+    std::string& str;
+
+    explicit Path(std::string& _str) : str(_str) {}
+
+    static const Path make(std::string& str) {
+        return Path(str);
+    }
+
+    template <class Archive>
+    void save(Archive& ar, const unsigned int) const {
+        auto s_path = SerializePath(str, true);
+        ar << s_path;
+    }
+    template <class Archive>
+    void load(Archive& ar, const unsigned int) const {
+        ar >> str;
+        str = SerializePath(str, false);
+    }
+
+    BOOST_SERIALIZATION_SPLIT_MEMBER();
+    friend class boost::serialization::access;
+};
+
 // FileSystem tree node/
 struct FSTEntry {
     bool isDirectory;
@@ -45,6 +77,17 @@ struct FSTEntry {
     std::string physicalName; // name on disk
     std::string virtualName;  // name in FST names table
     std::vector<FSTEntry> children;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& isDirectory;
+        ar& size;
+        ar& Path::make(physicalName);
+        ar& Path::make(virtualName);
+        ar& children;
+    }
+    friend class boost::serialization::access;
 };
 
 // Returns true if file filename exists
@@ -137,6 +180,8 @@ bool SetCurrentDir(const std::string& directory);
 
 void SetUserPath(const std::string& path = "");
 
+void SetCurrentRomPath(const std::string& path);
+
 // Returns a pointer to a string with a Citra data dir in the user's home
 // directory. To be used in "multi-user" mode (that is, installed).
 const std::string& GetUserPath(UserPath path);
@@ -221,7 +266,6 @@ public:
 
     void Swap(IOFile& other) noexcept;
 
-    bool Open(const std::string& filename, const char openmode[], int flags = 0);
     bool Close();
 
     template <typename T>
@@ -305,8 +349,31 @@ public:
     }
 
 private:
+    bool Open();
+
     std::FILE* m_file = nullptr;
     bool m_good = true;
+
+    std::string filename;
+    std::string openmode;
+    u32 flags;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& Path::make(filename);
+        ar& openmode;
+        ar& flags;
+        u64 pos;
+        if (Archive::is_saving::value) {
+            pos = Tell();
+        }
+        ar& pos;
+        if (Archive::is_loading::value) {
+            Open();
+            Seek(pos, SEEK_SET);
+        }
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileUtil
diff --git a/src/common/memory_ref.cpp b/src/common/memory_ref.cpp
new file mode 100644
index 000000000..300f87d58
--- /dev/null
+++ b/src/common/memory_ref.cpp
@@ -0,0 +1,8 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/archives.h"
+#include "common/memory_ref.h"
+
+SERIALIZE_EXPORT_IMPL(BufferMem)
diff --git a/src/common/memory_ref.h b/src/common/memory_ref.h
new file mode 100644
index 000000000..0a50c7be9
--- /dev/null
+++ b/src/common/memory_ref.h
@@ -0,0 +1,136 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <vector>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/vector.hpp>
+#include "common/assert.h"
+#include "common/common_types.h"
+
+/// Abstract host-side memory - for example a static buffer, or local vector
+class BackingMem {
+public:
+    virtual ~BackingMem() = default;
+    virtual u8* GetPtr() = 0;
+    virtual const u8* GetPtr() const = 0;
+    virtual std::size_t GetSize() const = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {}
+    friend class boost::serialization::access;
+};
+
+/// Backing memory implemented by a local buffer
+class BufferMem : public BackingMem {
+public:
+    BufferMem() = default;
+    explicit BufferMem(std::size_t size) : data(size) {}
+
+    u8* GetPtr() override {
+        return data.data();
+    }
+
+    const u8* GetPtr() const override {
+        return data.data();
+    }
+
+    std::size_t GetSize() const override {
+        return data.size();
+    }
+
+    std::vector<u8>& Vector() {
+        return data;
+    }
+
+    const std::vector<u8>& Vector() const {
+        return data;
+    }
+
+private:
+    std::vector<u8> data;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<BackingMem>(*this);
+        ar& data;
+    }
+    friend class boost::serialization::access;
+};
+
+BOOST_CLASS_EXPORT_KEY(BufferMem);
+
+/// A managed reference to host-side memory. Fast enough to be used everywhere instead of u8*
+/// Supports serialization.
+class MemoryRef {
+public:
+    MemoryRef() = default;
+    MemoryRef(std::nullptr_t) {}
+    MemoryRef(std::shared_ptr<BackingMem> backing_mem_)
+        : backing_mem(std::move(backing_mem_)), offset(0) {
+        Init();
+    }
+    MemoryRef(std::shared_ptr<BackingMem> backing_mem_, u64 offset_)
+        : backing_mem(std::move(backing_mem_)), offset(offset_) {
+        ASSERT(offset < backing_mem->GetSize());
+        Init();
+    }
+    explicit operator bool() const {
+        return cptr != nullptr;
+    }
+    operator u8*() {
+        return cptr;
+    }
+    u8* GetPtr() {
+        return cptr;
+    }
+    operator const u8*() const {
+        return cptr;
+    }
+    const u8* GetPtr() const {
+        return cptr;
+    }
+    std::size_t GetSize() const {
+        return csize;
+    }
+    MemoryRef& operator+=(u32 offset_by) {
+        ASSERT(offset_by < csize);
+        offset += offset_by;
+        Init();
+        return *this;
+    }
+    MemoryRef operator+(u32 offset_by) const {
+        ASSERT(offset_by < csize);
+        return MemoryRef(backing_mem, offset + offset_by);
+    }
+
+private:
+    std::shared_ptr<BackingMem> backing_mem{};
+    u64 offset{};
+    // Cached values for speed
+    u8* cptr{};
+    std::size_t csize{};
+
+    void Init() {
+        if (backing_mem) {
+            cptr = backing_mem->GetPtr() + offset;
+            csize = static_cast<std::size_t>(backing_mem->GetSize() - offset);
+        } else {
+            cptr = nullptr;
+            csize = 0;
+        }
+    }
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& backing_mem;
+        ar& offset;
+        Init();
+    }
+    friend class boost::serialization::access;
+};
diff --git a/src/common/serialization/atomic.h b/src/common/serialization/atomic.h
new file mode 100644
index 000000000..0cf0f20bc
--- /dev/null
+++ b/src/common/serialization/atomic.h
@@ -0,0 +1,29 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <boost/serialization/split_free.hpp>
+
+namespace boost::serialization {
+
+template <class Archive, class T>
+void serialize(Archive& ar, std::atomic<T>& value, const unsigned int file_version) {
+    boost::serialization::split_free(ar, value, file_version);
+}
+
+template <class Archive, class T>
+void save(Archive& ar, const std::atomic<T>& value, const unsigned int file_version) {
+    ar << value.load();
+}
+
+template <class Archive, class T>
+void load(Archive& ar, std::atomic<T>& value, const unsigned int file_version) {
+    T tmp;
+    ar >> tmp;
+    value.store(tmp);
+}
+
+} // namespace boost::serialization
diff --git a/src/common/serialization/boost_discrete_interval.hpp b/src/common/serialization/boost_discrete_interval.hpp
new file mode 100644
index 000000000..f04e3cabc
--- /dev/null
+++ b/src/common/serialization/boost_discrete_interval.hpp
@@ -0,0 +1,38 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <boost/icl/discrete_interval.hpp>
+#include "common/common_types.h"
+#include "common/logging/log.h"
+
+namespace boost::serialization {
+
+template <class Archive, class DomainT, ICL_COMPARE Compare>
+void save(Archive& ar, const boost::icl::discrete_interval<DomainT, Compare>& obj,
+          const unsigned int file_version) {
+    ar << obj.lower();
+    ar << obj.upper();
+    ar << obj.bounds()._bits;
+}
+
+template <class Archive, class DomainT, ICL_COMPARE Compare>
+void load(Archive& ar, boost::icl::discrete_interval<DomainT, Compare>& obj,
+          const unsigned int file_version) {
+    DomainT upper, lower;
+    boost::icl::bound_type bounds;
+    ar >> lower;
+    ar >> upper;
+    ar >> bounds;
+    obj = boost::icl::discrete_interval(lower, upper, boost::icl::interval_bounds(bounds));
+}
+
+template <class Archive, class DomainT, ICL_COMPARE Compare>
+void serialize(Archive& ar, boost::icl::discrete_interval<DomainT, Compare>& obj,
+               const unsigned int file_version) {
+    boost::serialization::split_free(ar, obj, file_version);
+}
+
+} // namespace boost::serialization
diff --git a/src/common/serialization/boost_flat_set.h b/src/common/serialization/boost_flat_set.h
new file mode 100644
index 000000000..703bd28b3
--- /dev/null
+++ b/src/common/serialization/boost_flat_set.h
@@ -0,0 +1,38 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <boost/container/flat_set.hpp>
+#include <boost/serialization/split_free.hpp>
+#include "common/common_types.h"
+
+namespace boost::serialization {
+
+template <class Archive, class T>
+void save(Archive& ar, const boost::container::flat_set<T>& set, const unsigned int file_version) {
+    ar << static_cast<u64>(set.size());
+    for (auto& v : set) {
+        ar << v;
+    }
+}
+
+template <class Archive, class T>
+void load(Archive& ar, boost::container::flat_set<T>& set, const unsigned int file_version) {
+    u64 count{};
+    ar >> count;
+    set.clear();
+    for (u64 i = 0; i < count; i++) {
+        T value{};
+        ar >> value;
+        set.insert(value);
+    }
+}
+
+template <class Archive, class T>
+void serialize(Archive& ar, boost::container::flat_set<T>& set, const unsigned int file_version) {
+    boost::serialization::split_free(ar, set, file_version);
+}
+
+} // namespace boost::serialization
diff --git a/src/common/serialization/boost_interval_set.hpp b/src/common/serialization/boost_interval_set.hpp
new file mode 100644
index 000000000..73a560360
--- /dev/null
+++ b/src/common/serialization/boost_interval_set.hpp
@@ -0,0 +1,38 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <boost/icl/interval_set.hpp>
+#include <boost/serialization/split_free.hpp>
+#include "common/serialization/boost_discrete_interval.hpp"
+
+namespace boost::serialization {
+
+template <class Archive, class T>
+void save(Archive& ar, const boost::icl::interval_set<T>& set, const unsigned int file_version) {
+    ar << static_cast<u64>(set.iterative_size());
+    for (auto& v : set) {
+        ar << v;
+    }
+}
+
+template <class Archive, class T>
+void load(Archive& ar, boost::icl::interval_set<T>& set, const unsigned int file_version) {
+    u64 count{};
+    ar >> count;
+    set.clear();
+    for (u64 i = 0; i < count; i++) {
+        typename boost::icl::interval_set<T>::interval_type value{};
+        ar >> value;
+        set += value;
+    }
+}
+
+template <class Archive, class T>
+void serialize(Archive& ar, boost::icl::interval_set<T>& set, const unsigned int file_version) {
+    boost::serialization::split_free(ar, set, file_version);
+}
+
+} // namespace boost::serialization
diff --git a/src/common/serialization/boost_small_vector.hpp b/src/common/serialization/boost_small_vector.hpp
new file mode 100644
index 000000000..b4e07a896
--- /dev/null
+++ b/src/common/serialization/boost_small_vector.hpp
@@ -0,0 +1,144 @@
+#ifndef BOOST_SERIALIZATION_BOOST_SMALL_VECTOR_HPP
+#define BOOST_SERIALIZATION_BOOST_SMALL_VECTOR_HPP
+
+// MS compatible compilers support #pragma once
+#if defined(_MSC_VER)
+#pragma once
+#endif
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// boost_vector.hpp: serialization for boost vector templates
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// fast array serialization (C) Copyright 2005 Matthias Troyer
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+//  See http://www.boost.org for updates, documentation, and revision history.
+
+#include <boost/container/small_vector.hpp>
+
+#include <boost/config.hpp>
+#include <boost/detail/workaround.hpp>
+
+#include <boost/archive/detail/basic_iarchive.hpp>
+#include <boost/serialization/access.hpp>
+#include <boost/serialization/collection_size_type.hpp>
+#include <boost/serialization/item_version_type.hpp>
+#include <boost/serialization/nvp.hpp>
+
+#include <boost/mpl/bool_fwd.hpp>
+#include <boost/mpl/if.hpp>
+#include <boost/serialization/array_wrapper.hpp>
+#include <boost/serialization/collections_load_imp.hpp>
+#include <boost/serialization/collections_save_imp.hpp>
+#include <boost/serialization/split_free.hpp>
+
+// default is being compatible with version 1.34.1 files, not 1.35 files
+#ifndef BOOST_SERIALIZATION_VECTOR_VERSIONED
+#define BOOST_SERIALIZATION_VECTOR_VERSIONED(V) (V == 4 || V == 5)
+#endif
+
+namespace boost {
+namespace serialization {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// vector< T >
+
+// the default versions
+
+template <class Archive, class U, std::size_t N>
+inline void save(Archive& ar, const boost::container::small_vector<U, N>& t,
+                 const unsigned int /* file_version */, mpl::false_) {
+    boost::serialization::stl::save_collection<Archive, boost::container::small_vector<U, N>>(ar,
+                                                                                              t);
+}
+
+template <class Archive, class U, std::size_t N>
+inline void load(Archive& ar, boost::container::small_vector<U, N>& t,
+                 const unsigned int /* file_version */, mpl::false_) {
+    const boost::archive::library_version_type library_version(ar.get_library_version());
+    // retrieve number of elements
+    item_version_type item_version(0);
+    collection_size_type count;
+    ar >> BOOST_SERIALIZATION_NVP(count);
+    if (boost::archive::library_version_type(3) < library_version) {
+        ar >> BOOST_SERIALIZATION_NVP(item_version);
+    }
+    t.reserve(count);
+    stl::collection_load_impl(ar, t, count, item_version);
+}
+
+// the optimized versions
+
+template <class Archive, class U, std::size_t N>
+inline void save(Archive& ar, const boost::container::small_vector<U, N>& t,
+                 const unsigned int /* file_version */, mpl::true_) {
+    const collection_size_type count(t.size());
+    ar << BOOST_SERIALIZATION_NVP(count);
+    if (!t.empty())
+        // explict template arguments to pass intel C++ compiler
+        ar << serialization::make_array<const U, collection_size_type>(static_cast<const U*>(&t[0]),
+                                                                       count);
+}
+
+template <class Archive, class U, std::size_t N>
+inline void load(Archive& ar, boost::container::small_vector<U, N>& t,
+                 const unsigned int /* file_version */, mpl::true_) {
+    collection_size_type count(t.size());
+    ar >> BOOST_SERIALIZATION_NVP(count);
+    t.resize(count);
+    unsigned int item_version = 0;
+    if (BOOST_SERIALIZATION_VECTOR_VERSIONED(ar.get_library_version())) {
+        ar >> BOOST_SERIALIZATION_NVP(item_version);
+    }
+    if (!t.empty())
+        // explict template arguments to pass intel C++ compiler
+        ar >> serialization::make_array<U, collection_size_type>(static_cast<U*>(&t[0]), count);
+}
+
+// dispatch to either default or optimized versions
+
+template <class Archive, class U, std::size_t N>
+inline void save(Archive& ar, const boost::container::small_vector<U, N>& t,
+                 const unsigned int file_version) {
+    typedef typename boost::serialization::use_array_optimization<Archive>::template apply<
+        typename remove_const<U>::type>::type use_optimized;
+    save(ar, t, file_version, use_optimized());
+}
+
+template <class Archive, class U, std::size_t N>
+inline void load(Archive& ar, boost::container::small_vector<U, N>& t,
+                 const unsigned int file_version) {
+#ifdef BOOST_SERIALIZATION_VECTOR_135_HPP
+    if (ar.get_library_version() == boost::archive::library_version_type(5)) {
+        load(ar, t, file_version, boost::is_arithmetic<U>());
+        return;
+    }
+#endif
+    typedef typename boost::serialization::use_array_optimization<Archive>::template apply<
+        typename remove_const<U>::type>::type use_optimized;
+    load(ar, t, file_version, use_optimized());
+}
+
+// split non-intrusive serialization function member into separate
+// non intrusive save/load member functions
+template <class Archive, class U, std::size_t N>
+inline void serialize(Archive& ar, boost::container::small_vector<U, N>& t,
+                      const unsigned int file_version) {
+    boost::serialization::split_free(ar, t, file_version);
+}
+
+// split non-intrusive serialization function member into separate
+// non intrusive save/load member functions
+template <class Archive, std::size_t N>
+inline void serialize(Archive& ar, boost::container::small_vector<bool, N>& t,
+                      const unsigned int file_version) {
+    boost::serialization::split_free(ar, t, file_version);
+}
+
+} // namespace serialization
+} // namespace boost
+
+#endif // BOOST_SERIALIZATION_BOOST_SMALL_VECTOR_HPP
diff --git a/src/common/serialization/boost_vector.hpp b/src/common/serialization/boost_vector.hpp
new file mode 100644
index 000000000..55a5b9eae
--- /dev/null
+++ b/src/common/serialization/boost_vector.hpp
@@ -0,0 +1,149 @@
+#ifndef BOOST_SERIALIZATION_BOOST_VECTOR_HPP
+#define BOOST_SERIALIZATION_BOOST_VECTOR_HPP
+
+// MS compatible compilers support #pragma once
+#if defined(_MSC_VER)
+#pragma once
+#endif
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// boost_vector.hpp: serialization for boost vector templates
+
+// (C) Copyright 2002 Robert Ramey - http://www.rrsd.com .
+// fast array serialization (C) Copyright 2005 Matthias Troyer
+// Use, modification and distribution is subject to the Boost Software
+// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
+// http://www.boost.org/LICENSE_1_0.txt)
+
+//  See http://www.boost.org for updates, documentation, and revision history.
+
+#include <boost/container/vector.hpp>
+
+#include <boost/config.hpp>
+#include <boost/detail/workaround.hpp>
+
+#include <boost/archive/detail/basic_iarchive.hpp>
+#include <boost/serialization/access.hpp>
+#include <boost/serialization/collection_size_type.hpp>
+#include <boost/serialization/item_version_type.hpp>
+#include <boost/serialization/nvp.hpp>
+
+#include <boost/mpl/bool_fwd.hpp>
+#include <boost/mpl/if.hpp>
+#include <boost/serialization/array_wrapper.hpp>
+#include <boost/serialization/collections_load_imp.hpp>
+#include <boost/serialization/collections_save_imp.hpp>
+#include <boost/serialization/split_free.hpp>
+
+// default is being compatible with version 1.34.1 files, not 1.35 files
+#ifndef BOOST_SERIALIZATION_VECTOR_VERSIONED
+#define BOOST_SERIALIZATION_VECTOR_VERSIONED(V) (V == 4 || V == 5)
+#endif
+
+namespace boost {
+namespace serialization {
+
+/////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
+// vector< T >
+
+// the default versions
+
+template <class Archive, class U, class Allocator, class Options>
+inline void save(Archive& ar, const boost::container::vector<U, Allocator, Options>& t,
+                 const unsigned int /* file_version */, mpl::false_) {
+    boost::serialization::stl::save_collection<Archive,
+                                               boost::container::vector<U, Allocator, Options>>(ar,
+                                                                                                t);
+}
+
+template <class Archive, class U, class Allocator, class Options>
+inline void load(Archive& ar, boost::container::vector<U, Allocator, Options>& t,
+                 const unsigned int /* file_version */, mpl::false_) {
+    const boost::archive::library_version_type library_version(ar.get_library_version());
+    // retrieve number of elements
+    item_version_type item_version(0);
+    collection_size_type count;
+    ar >> BOOST_SERIALIZATION_NVP(count);
+    if (boost::archive::library_version_type(3) < library_version) {
+        ar >> BOOST_SERIALIZATION_NVP(item_version);
+    }
+    t.reserve(count);
+    stl::collection_load_impl(ar, t, count, item_version);
+}
+
+// the optimized versions
+
+template <class Archive, class U, class Allocator, class Options>
+inline void save(Archive& ar, const boost::container::vector<U, Allocator, Options>& t,
+                 const unsigned int /* file_version */, mpl::true_) {
+    const collection_size_type count(t.size());
+    ar << BOOST_SERIALIZATION_NVP(count);
+    if (!t.empty())
+        // explict template arguments to pass intel C++ compiler
+        ar << serialization::make_array<const U, collection_size_type>(static_cast<const U*>(&t[0]),
+                                                                       count);
+}
+
+template <class Archive, class U, class Allocator, class Options>
+inline void load(Archive& ar, boost::container::vector<U, Allocator, Options>& t,
+                 const unsigned int /* file_version */, mpl::true_) {
+    collection_size_type count(t.size());
+    ar >> BOOST_SERIALIZATION_NVP(count);
+    t.resize(count);
+    unsigned int item_version = 0;
+    if (BOOST_SERIALIZATION_VECTOR_VERSIONED(ar.get_library_version())) {
+        ar >> BOOST_SERIALIZATION_NVP(item_version);
+    }
+    if (!t.empty())
+        // explict template arguments to pass intel C++ compiler
+        ar >> serialization::make_array<U, collection_size_type>(static_cast<U*>(&t[0]), count);
+}
+
+// dispatch to either default or optimized versions
+
+template <class Archive, class U, class Allocator, class Options>
+inline void save(Archive& ar, const boost::container::vector<U, Allocator, Options>& t,
+                 const unsigned int file_version) {
+    typedef typename boost::serialization::use_array_optimization<Archive>::template apply<
+        typename remove_const<U>::type>::type use_optimized;
+    save(ar, t, file_version, use_optimized());
+}
+
+template <class Archive, class U, class Allocator, class Options>
+inline void load(Archive& ar, boost::container::vector<U, Allocator, Options>& t,
+                 const unsigned int file_version) {
+#ifdef BOOST_SERIALIZATION_VECTOR_135_HPP
+    if (ar.get_library_version() == boost::archive::library_version_type(5)) {
+        load(ar, t, file_version, boost::is_arithmetic<U>());
+        return;
+    }
+#endif
+    typedef typename boost::serialization::use_array_optimization<Archive>::template apply<
+        typename remove_const<U>::type>::type use_optimized;
+    load(ar, t, file_version, use_optimized());
+}
+
+// split non-intrusive serialization function member into separate
+// non intrusive save/load member functions
+template <class Archive, class U, class Allocator, class Options>
+inline void serialize(Archive& ar, boost::container::vector<U, Allocator, Options>& t,
+                      const unsigned int file_version) {
+    boost::serialization::split_free(ar, t, file_version);
+}
+
+// split non-intrusive serialization function member into separate
+// non intrusive save/load member functions
+template <class Archive, class Allocator, class Options>
+inline void serialize(Archive& ar, boost::container::vector<bool, Allocator, Options>& t,
+                      const unsigned int file_version) {
+    boost::serialization::split_free(ar, t, file_version);
+}
+
+} // namespace serialization
+} // namespace boost
+
+#include <boost/serialization/collection_traits.hpp>
+
+BOOST_SERIALIZATION_COLLECTION_TRAITS(boost::container::vector)
+
+#endif // BOOST_SERIALIZATION_BOOST_VECTOR_HPP
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index 62e48e224..af40bf09b 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -7,6 +7,9 @@
 #include <algorithm>
 #include <array>
 #include <deque>
+#include <boost/serialization/deque.hpp>
+#include <boost/serialization/split_member.hpp>
+#include "common/common_types.h"
 
 namespace Common {
 
@@ -157,6 +160,52 @@ private:
     Queue* first;
     // The priority level queues of thread ids.
     std::array<Queue, NUM_QUEUES> queues;
+
+    s64 ToIndex(const Queue* q) const {
+        if (q == nullptr) {
+            return -2;
+        } else if (q == UnlinkedTag()) {
+            return -1;
+        } else {
+            return q - queues.data();
+        }
+    }
+
+    Queue* ToPointer(s64 idx) {
+        if (idx == -1) {
+            return UnlinkedTag();
+        } else if (idx < 0) {
+            return nullptr;
+        } else {
+            return &queues[idx];
+        }
+    }
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void save(Archive& ar, const unsigned int file_version) const {
+        const s64 idx = ToIndex(first);
+        ar << idx;
+        for (std::size_t i = 0; i < NUM_QUEUES; i++) {
+            const s64 idx1 = ToIndex(queues[i].next_nonempty);
+            ar << idx1;
+            ar << queues[i].data;
+        }
+    }
+
+    template <class Archive>
+    void load(Archive& ar, const unsigned int file_version) {
+        s64 idx;
+        ar >> idx;
+        first = ToPointer(idx);
+        for (std::size_t i = 0; i < NUM_QUEUES; i++) {
+            ar >> idx;
+            queues[i].next_nonempty = ToPointer(idx);
+            ar >> queues[i].data;
+        }
+    }
+
+    BOOST_SERIALIZATION_SPLIT_MEMBER()
 };
 
 } // namespace Common
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index ba36744fc..ba7bd1aa7 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -32,6 +32,7 @@
 
 #include <cmath>
 #include <type_traits>
+#include <boost/serialization/access.hpp>
 
 namespace Common {
 
@@ -44,6 +45,13 @@ class Vec4;
 
 template <typename T>
 class Vec2 {
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& x;
+        ar& y;
+    }
+
 public:
     T x;
     T y;
@@ -191,6 +199,14 @@ inline float Vec2<float>::Normalize() {
 
 template <typename T>
 class Vec3 {
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& x;
+        ar& y;
+        ar& z;
+    }
+
 public:
     T x;
     T y;
@@ -399,6 +415,15 @@ using Vec3f = Vec3<float>;
 
 template <typename T>
 class Vec4 {
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& x;
+        ar& y;
+        ar& z;
+        ar& w;
+    }
+
 public:
     T x;
     T y;
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index d70f21888..01f18e445 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -164,6 +164,7 @@ add_library(core STATIC
     hle/kernel/server_session.cpp
     hle/kernel/server_session.h
     hle/kernel/session.h
+    hle/kernel/session.cpp
     hle/kernel/shared_memory.cpp
     hle/kernel/shared_memory.h
     hle/kernel/shared_page.cpp
@@ -450,6 +451,8 @@ add_library(core STATIC
     rpc/server.h
     rpc/udp_server.cpp
     rpc/udp_server.h
+    savestate.cpp
+    savestate.h
     settings.cpp
     settings.h
     telemetry_session.cpp
@@ -469,7 +472,7 @@ endif()
 create_target_directory_groups(core)
 
 target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp fmt open_source_archives Boost::serialization)
 
 if (ENABLE_WEB_SERVICE)
     get_directory_property(OPENSSL_LIBS
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h
index 6595b53c4..40bfc5543 100644
--- a/src/core/arm/arm_interface.h
+++ b/src/core/arm/arm_interface.h
@@ -6,10 +6,14 @@
 
 #include <cstddef>
 #include <memory>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/split_member.hpp>
+#include <boost/serialization/version.hpp>
 #include "common/common_types.h"
 #include "core/arm/skyeye_common/arm_regformat.h"
 #include "core/arm/skyeye_common/vfp/asm_vfp.h"
 #include "core/core_timing.h"
+#include "core/memory.h"
 
 namespace Memory {
 struct PageTable;
@@ -23,6 +27,48 @@ public:
     virtual ~ARM_Interface() {}
 
     class ThreadContext {
+        friend class boost::serialization::access;
+
+        template <class Archive>
+        void save(Archive& ar, const unsigned int file_version) const {
+            for (std::size_t i = 0; i < 16; i++) {
+                const auto r = GetCpuRegister(i);
+                ar << r;
+            }
+            std::size_t fpu_reg_count = file_version == 0 ? 16 : 64;
+            for (std::size_t i = 0; i < fpu_reg_count; i++) {
+                const auto r = GetFpuRegister(i);
+                ar << r;
+            }
+            const auto r1 = GetCpsr();
+            ar << r1;
+            const auto r2 = GetFpscr();
+            ar << r2;
+            const auto r3 = GetFpexc();
+            ar << r3;
+        }
+
+        template <class Archive>
+        void load(Archive& ar, const unsigned int file_version) {
+            u32 r;
+            for (std::size_t i = 0; i < 16; i++) {
+                ar >> r;
+                SetCpuRegister(i, r);
+            }
+            std::size_t fpu_reg_count = file_version == 0 ? 16 : 64;
+            for (std::size_t i = 0; i < fpu_reg_count; i++) {
+                ar >> r;
+                SetFpuRegister(i, r);
+            }
+            ar >> r;
+            SetCpsr(r);
+            ar >> r;
+            SetFpscr(r);
+            ar >> r;
+            SetFpexc(r);
+        }
+
+        BOOST_SERIALIZATION_SPLIT_MEMBER()
     public:
         virtual ~ThreadContext() = default;
 
@@ -77,7 +123,7 @@ public:
     virtual void InvalidateCacheRange(u32 start_address, std::size_t length) = 0;
 
     /// Notify CPU emulation that page tables have changed
-    virtual void PageTableChanged(Memory::PageTable* new_page_table) = 0;
+    virtual void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) = 0;
 
     /**
      * Set the Program Counter to an address
@@ -150,7 +196,7 @@ public:
      * @param reg The CP15 register to retrieve the value from.
      * @return the value stored in the given CP15 register.
      */
-    virtual u32 GetCP15Register(CP15Register reg) = 0;
+    virtual u32 GetCP15Register(CP15Register reg) const = 0;
 
     /**
      * Stores the given value into the indicated CP15 register.
@@ -180,6 +226,8 @@ public:
     /// Prepare core for thread reschedule (if needed to correctly handle state)
     virtual void PrepareReschedule() = 0;
 
+    virtual void PurgeState() = 0;
+
     std::shared_ptr<Core::Timing::Timer> GetTimer() {
         return timer;
     }
@@ -189,8 +237,101 @@ public:
     }
 
 protected:
+    // This us used for serialization. Returning nullptr is valid if page tables are not used.
+    virtual std::shared_ptr<Memory::PageTable> GetPageTable() const = 0;
+
     std::shared_ptr<Core::Timing::Timer> timer;
 
 private:
     u32 id;
+
+    friend class boost::serialization::access;
+
+    template <class Archive>
+    void save(Archive& ar, const unsigned int file_version) const {
+        ar << timer;
+        ar << id;
+        const auto page_table = GetPageTable();
+        ar << page_table;
+        for (int i = 0; i < 15; i++) {
+            const auto r = GetReg(i);
+            ar << r;
+        }
+        const auto pc = GetPC();
+        ar << pc;
+        const auto cpsr = GetCPSR();
+        ar << cpsr;
+        int vfp_reg_count = file_version == 0 ? 32 : 64;
+        for (int i = 0; i < vfp_reg_count; i++) {
+            const auto r = GetVFPReg(i);
+            ar << r;
+        }
+        for (std::size_t i = 0; i < VFPSystemRegister::VFP_SYSTEM_REGISTER_COUNT; i++) {
+            const auto reg = static_cast<VFPSystemRegister>(i);
+            u32 r = 0;
+            switch (reg) {
+            case VFP_FPSCR:
+            case VFP_FPEXC:
+                r = GetVFPSystemReg(reg);
+            }
+            ar << r;
+        }
+        for (std::size_t i = 0; i < CP15Register::CP15_REGISTER_COUNT; i++) {
+            const auto reg = static_cast<CP15Register>(i);
+            u32 r = 0;
+            switch (reg) {
+            case CP15_THREAD_UPRW:
+            case CP15_THREAD_URO:
+                r = GetCP15Register(reg);
+            }
+            ar << r;
+        }
+    }
+
+    template <class Archive>
+    void load(Archive& ar, const unsigned int file_version) {
+        PurgeState();
+        ar >> timer;
+        ar >> id;
+        std::shared_ptr<Memory::PageTable> page_table{};
+        ar >> page_table;
+        SetPageTable(page_table);
+        u32 r;
+        for (int i = 0; i < 15; i++) {
+            ar >> r;
+            SetReg(i, r);
+        }
+        ar >> r;
+        SetPC(r);
+        ar >> r;
+        SetCPSR(r);
+        int vfp_reg_count = file_version == 0 ? 32 : 64;
+        for (int i = 0; i < vfp_reg_count; i++) {
+            ar >> r;
+            SetVFPReg(i, r);
+        }
+        for (std::size_t i = 0; i < VFPSystemRegister::VFP_SYSTEM_REGISTER_COUNT; i++) {
+            ar >> r;
+            const auto reg = static_cast<VFPSystemRegister>(i);
+            switch (reg) {
+            case VFP_FPSCR:
+            case VFP_FPEXC:
+                SetVFPSystemReg(reg, r);
+            }
+        }
+        for (std::size_t i = 0; i < CP15Register::CP15_REGISTER_COUNT; i++) {
+            ar >> r;
+            const auto reg = static_cast<CP15Register>(i);
+            switch (reg) {
+            case CP15_THREAD_UPRW:
+            case CP15_THREAD_URO:
+                SetCP15Register(reg, r);
+            }
+        }
+    }
+
+    BOOST_SERIALIZATION_SPLIT_MEMBER()
 };
+
+BOOST_CLASS_VERSION(ARM_Interface, 1)
+BOOST_CLASS_VERSION(ARM_Interface::ThreadContext, 1)
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index df5de7131..039601a54 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -153,7 +153,7 @@ ARM_Dynarmic::ARM_Dynarmic(Core::System* system, Memory::MemorySystem& memory, u
                            std::shared_ptr<Core::Timing::Timer> timer)
     : ARM_Interface(id, timer), system(*system), memory(memory),
       cb(std::make_unique<DynarmicUserCallbacks>(*this)) {
-    PageTableChanged(memory.GetCurrentPageTable());
+    SetPageTable(memory.GetCurrentPageTable());
 }
 
 ARM_Dynarmic::~ARM_Dynarmic() = default;
@@ -229,7 +229,7 @@ void ARM_Dynarmic::SetCPSR(u32 cpsr) {
     jit->SetCpsr(cpsr);
 }
 
-u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) {
+u32 ARM_Dynarmic::GetCP15Register(CP15Register reg) const {
     switch (reg) {
     case CP15_THREAD_UPRW:
         return cp15_state.cp15_thread_uprw;
@@ -287,17 +287,27 @@ void ARM_Dynarmic::InvalidateCacheRange(u32 start_address, std::size_t length) {
     jit->InvalidateCacheRange(start_address, length);
 }
 
-void ARM_Dynarmic::PageTableChanged(Memory::PageTable* new_page_table) {
-    current_page_table = new_page_table;
+std::shared_ptr<Memory::PageTable> ARM_Dynarmic::GetPageTable() const {
+    return current_page_table;
+}
+
+void ARM_Dynarmic::SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) {
+    current_page_table = page_table;
+    Dynarmic::A32::Context ctx{};
+    if (jit) {
+        jit->SaveContext(ctx);
+    }
 
     auto iter = jits.find(current_page_table);
     if (iter != jits.end()) {
         jit = iter->second.get();
+        jit->LoadContext(ctx);
         return;
     }
 
     auto new_jit = MakeJit();
     jit = new_jit.get();
+    jit->LoadContext(ctx);
     jits.emplace(current_page_table, std::move(new_jit));
 }
 
@@ -311,8 +321,12 @@ void ARM_Dynarmic::ServeBreak() {
 std::unique_ptr<Dynarmic::A32::Jit> ARM_Dynarmic::MakeJit() {
     Dynarmic::A32::UserConfig config;
     config.callbacks = cb.get();
-    config.page_table = &current_page_table->pointers;
+    config.page_table = &current_page_table->GetPointerArray();
     config.coprocessors[15] = std::make_shared<DynarmicCP15>(cp15_state);
     config.define_unpredictable_behaviour = true;
     return std::make_unique<Dynarmic::A32::Jit>(config);
 }
+
+void ARM_Dynarmic::PurgeState() {
+    ClearInstructionCache();
+}
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 44f2ee375..7575e9a2a 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -41,7 +41,7 @@ public:
     void SetVFPSystemReg(VFPSystemRegister reg, u32 value) override;
     u32 GetCPSR() const override;
     void SetCPSR(u32 cpsr) override;
-    u32 GetCP15Register(CP15Register reg) override;
+    u32 GetCP15Register(CP15Register reg) const override;
     void SetCP15Register(CP15Register reg, u32 value) override;
 
     std::unique_ptr<ThreadContext> NewContext() const override;
@@ -52,7 +52,11 @@ public:
 
     void ClearInstructionCache() override;
     void InvalidateCacheRange(u32 start_address, std::size_t length) override;
-    void PageTableChanged(Memory::PageTable* new_page_table) override;
+    void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
+    void PurgeState() override;
+
+protected:
+    std::shared_ptr<Memory::PageTable> GetPageTable() const override;
 
 private:
     void ServeBreak();
@@ -67,6 +71,6 @@ private:
     CP15State cp15_state;
 
     Dynarmic::A32::Jit* jit = nullptr;
-    Memory::PageTable* current_page_table = nullptr;
-    std::map<Memory::PageTable*, std::unique_ptr<Dynarmic::A32::Jit>> jits;
+    std::shared_ptr<Memory::PageTable> current_page_table = nullptr;
+    std::map<std::shared_ptr<Memory::PageTable>, std::unique_ptr<Dynarmic::A32::Jit>> jits;
 };
diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp
index c2c291df0..099a5c06a 100644
--- a/src/core/arm/dyncom/arm_dyncom.cpp
+++ b/src/core/arm/dyncom/arm_dyncom.cpp
@@ -95,10 +95,16 @@ void ARM_DynCom::InvalidateCacheRange(u32, std::size_t) {
     ClearInstructionCache();
 }
 
-void ARM_DynCom::PageTableChanged(Memory::PageTable*) {
+void ARM_DynCom::SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) {
     ClearInstructionCache();
 }
 
+std::shared_ptr<Memory::PageTable> ARM_DynCom::GetPageTable() const {
+    return nullptr;
+}
+
+void ARM_DynCom::PurgeState() {}
+
 void ARM_DynCom::SetPC(u32 pc) {
     state->Reg[15] = pc;
 }
@@ -139,7 +145,7 @@ void ARM_DynCom::SetCPSR(u32 cpsr) {
     state->Cpsr = cpsr;
 }
 
-u32 ARM_DynCom::GetCP15Register(CP15Register reg) {
+u32 ARM_DynCom::GetCP15Register(CP15Register reg) const {
     return state->CP15[reg];
 }
 
diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h
index 38659eac0..1452216c2 100644
--- a/src/core/arm/dyncom/arm_dyncom.h
+++ b/src/core/arm/dyncom/arm_dyncom.h
@@ -30,7 +30,6 @@ public:
 
     void ClearInstructionCache() override;
     void InvalidateCacheRange(u32 start_address, std::size_t length) override;
-    void PageTableChanged(Memory::PageTable* new_page_table) override;
 
     void SetPC(u32 pc) override;
     u32 GetPC() const override;
@@ -42,14 +41,19 @@ public:
     void SetVFPSystemReg(VFPSystemRegister reg, u32 value) override;
     u32 GetCPSR() const override;
     void SetCPSR(u32 cpsr) override;
-    u32 GetCP15Register(CP15Register reg) override;
+    u32 GetCP15Register(CP15Register reg) const override;
     void SetCP15Register(CP15Register reg, u32 value) override;
 
     std::unique_ptr<ThreadContext> NewContext() const override;
     void SaveContext(const std::unique_ptr<ThreadContext>& arg) override;
     void LoadContext(const std::unique_ptr<ThreadContext>& arg) override;
 
+    void SetPageTable(const std::shared_ptr<Memory::PageTable>& page_table) override;
     void PrepareReschedule() override;
+    void PurgeState() override;
+
+protected:
+    std::shared_ptr<Memory::PageTable> GetPageTable() const override;
 
 private:
     void ExecuteInstructions(u64 num_instructions);
diff --git a/src/core/cheats/cheats.cpp b/src/core/cheats/cheats.cpp
index 8b4a30ca6..9053b5ca6 100644
--- a/src/core/cheats/cheats.cpp
+++ b/src/core/cheats/cheats.cpp
@@ -18,6 +18,10 @@ constexpr u64 run_interval_ticks = BASE_CLOCK_RATE_ARM11 / 60;
 
 CheatEngine::CheatEngine(Core::System& system_) : system(system_) {
     LoadCheatFile();
+    Connect();
+}
+
+void CheatEngine::Connect() {
     event = system.CoreTiming().RegisterEvent(
         "CheatCore::run_event",
         [this](u64 thread_id, s64 cycle_late) { RunCallback(thread_id, cycle_late); });
diff --git a/src/core/cheats/cheats.h b/src/core/cheats/cheats.h
index a8d373038..1dfed7ab8 100644
--- a/src/core/cheats/cheats.h
+++ b/src/core/cheats/cheats.h
@@ -26,6 +26,7 @@ class CheatEngine {
 public:
     explicit CheatEngine(Core::System& system);
     ~CheatEngine();
+    void Connect();
     std::vector<std::shared_ptr<CheatBase>> GetCheats() const;
     void AddCheat(const std::shared_ptr<CheatBase>& cheat);
     void RemoveCheat(int index);
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 3f3478aa4..03b70d2a8 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <fstream>
 #include <memory>
+#include <stdexcept>
 #include <utility>
+#include <boost/serialization/array.hpp>
 #include "audio_core/dsp_interface.h"
 #include "audio_core/hle/hle.h"
 #include "audio_core/lle/lle.h"
@@ -23,25 +26,48 @@
 #endif
 #include "core/custom_tex_cache.h"
 #include "core/gdbstub/gdbstub.h"
+#include "core/global.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/kernel/thread.h"
 #include "core/hle/service/fs/archive.h"
+#include "core/hle/service/gsp/gsp.h"
+#include "core/hle/service/pm/pm_app.h"
 #include "core/hle/service/service.h"
 #include "core/hle/service/sm/sm.h"
+#include "core/hw/gpu.h"
 #include "core/hw/hw.h"
+#include "core/hw/lcd.h"
 #include "core/loader/loader.h"
 #include "core/movie.h"
 #include "core/rpc/rpc_server.h"
 #include "core/settings.h"
 #include "network/network.h"
+#include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 
 namespace Core {
 
 /*static*/ System System::s_instance;
 
+template <>
+Core::System& Global() {
+    return System::GetInstance();
+}
+
+template <>
+Kernel::KernelSystem& Global() {
+    return System::GetInstance().Kernel();
+}
+
+template <>
+Core::Timing& Global() {
+    return System::GetInstance().CoreTiming();
+}
+
+System::~System() = default;
+
 System::ResultStatus System::RunLoop(bool tight_loop) {
     status = ResultStatus::Success;
     if (std::any_of(cpu_cores.begin(), cpu_cores.end(),
@@ -67,6 +93,52 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
         }
     }
 
+    Signal signal{Signal::None};
+    u32 param{};
+    {
+        std::lock_guard lock{signal_mutex};
+        if (current_signal != Signal::None) {
+            signal = current_signal;
+            param = signal_param;
+            current_signal = Signal::None;
+        }
+    }
+    switch (signal) {
+    case Signal::Reset:
+        Reset();
+        return ResultStatus::Success;
+    case Signal::Shutdown:
+        return ResultStatus::ShutdownRequested;
+    case Signal::Load: {
+        LOG_INFO(Core, "Begin load");
+        try {
+            System::LoadState(param);
+            LOG_INFO(Core, "Load completed");
+        } catch (const std::exception& e) {
+            LOG_ERROR(Core, "Error loading: {}", e.what());
+            status_details = e.what();
+            return ResultStatus::ErrorSavestate;
+        }
+        frame_limiter.WaitOnce();
+        return ResultStatus::Success;
+    }
+    case Signal::Save: {
+        LOG_INFO(Core, "Begin save");
+        try {
+            System::SaveState(param);
+            LOG_INFO(Core, "Save completed");
+        } catch (const std::exception& e) {
+            LOG_ERROR(Core, "Error saving: {}", e.what());
+            status_details = e.what();
+            return ResultStatus::ErrorSavestate;
+        }
+        frame_limiter.WaitOnce();
+        return ResultStatus::Success;
+    }
+    default:
+        break;
+    }
+
     // All cores should have executed the same amount of ticks. If this is not the case an event was
     // scheduled with a cycles_into_future smaller then the current downcount.
     // So we have to get those cores to the same global time first
@@ -141,20 +213,26 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
     HW::Update();
     Reschedule();
 
-    if (reset_requested.exchange(false)) {
-        Reset();
-    } else if (shutdown_requested.exchange(false)) {
-        return ResultStatus::ShutdownRequested;
-    }
-
     return status;
 }
 
+bool System::SendSignal(System::Signal signal, u32 param) {
+    std::lock_guard lock{signal_mutex};
+    if (current_signal != signal && current_signal != Signal::None) {
+        LOG_ERROR(Core, "Unable to {} as {} is ongoing", signal, current_signal);
+        return false;
+    }
+    current_signal = signal;
+    signal_param = param;
+    return true;
+}
+
 System::ResultStatus System::SingleStep() {
     return RunLoop(false);
 }
 
 System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
+    FileUtil::SetCurrentRomPath(filepath);
     app_loader = Loader::GetLoader(filepath);
     if (!app_loader) {
         LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
@@ -180,7 +258,11 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
     ASSERT(system_mode.first);
     auto n3ds_mode = app_loader->LoadKernelN3dsMode();
     ASSERT(n3ds_mode.first);
-    ResultStatus init_result{Init(emu_window, *system_mode.first, *n3ds_mode.first)};
+    u32 num_cores = 2;
+    if (Settings::values.is_new_3ds) {
+        num_cores = 4;
+    }
+    ResultStatus init_result{Init(emu_window, *system_mode.first, *n3ds_mode.first, num_cores)};
     if (init_result != ResultStatus::Success) {
         LOG_CRITICAL(Core, "Failed to initialize system (Error {})!",
                      static_cast<u32>(init_result));
@@ -206,7 +288,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
         }
     }
     cheat_engine = std::make_unique<Cheats::CheatEngine>(*this);
-    u64 title_id{0};
+    title_id = 0;
     if (app_loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
         LOG_ERROR(Core, "Failed to find title id for ROM (Error {})",
                   static_cast<u32>(load_result));
@@ -237,7 +319,8 @@ void System::PrepareReschedule() {
 }
 
 PerfStats::Results System::GetAndResetPerfStats() {
-    return perf_stats->GetAndResetStats(timing->GetGlobalTimeUs());
+    return (perf_stats && timing) ? perf_stats->GetAndResetStats(timing->GetGlobalTimeUs())
+                                  : PerfStats::Results{};
 }
 
 void System::Reschedule() {
@@ -252,14 +335,10 @@ void System::Reschedule() {
     }
 }
 
-System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode) {
+System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode,
+                                  u32 num_cores) {
     LOG_DEBUG(HW_Memory, "initialized OK");
 
-    std::size_t num_cores = 2;
-    if (Settings::values.is_new_3ds) {
-        num_cores = 4;
-    }
-
     memory = std::make_unique<Memory::MemorySystem>();
 
     timing = std::make_unique<Timing>(num_cores, Settings::values.cpu_clock_percentage);
@@ -269,19 +348,19 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
 
     if (Settings::values.use_cpu_jit) {
 #ifdef ARCHITECTURE_x86_64
-        for (std::size_t i = 0; i < num_cores; ++i) {
+        for (u32 i = 0; i < num_cores; ++i) {
             cpu_cores.push_back(
                 std::make_shared<ARM_Dynarmic>(this, *memory, i, timing->GetTimer(i)));
         }
 #else
-        for (std::size_t i = 0; i < num_cores; ++i) {
+        for (u32 i = 0; i < num_cores; ++i) {
             cpu_cores.push_back(
                 std::make_shared<ARM_DynCom>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
         }
         LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
 #endif
     } else {
-        for (std::size_t i = 0; i < num_cores; ++i) {
+        for (u32 i = 0; i < num_cores; ++i) {
             cpu_cores.push_back(
                 std::make_shared<ARM_DynCom>(this, *memory, USER32MODE, i, timing->GetTimer(i)));
         }
@@ -307,7 +386,7 @@ System::ResultStatus System::Init(Frontend::EmuWindow& emu_window, u32 system_mo
 
     rpc_server = std::make_unique<RPC::RPCServer>();
 
-    service_manager = std::make_shared<Service::SM::ServiceManager>(*this);
+    service_manager = std::make_unique<Service::SM::ServiceManager>(*this);
     archive_manager = std::make_unique<Service::FS::ArchiveManager>(*this);
 
     HW::Init(*memory);
@@ -419,7 +498,7 @@ void System::RegisterImageInterface(std::shared_ptr<Frontend::ImageInterface> im
     registered_image_interface = std::move(image_interface);
 }
 
-void System::Shutdown() {
+void System::Shutdown(bool is_deserializing) {
     // Log last frame performance stats
     const auto perf_results = GetAndResetPerfStats();
     telemetry_session->AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
@@ -432,20 +511,22 @@ void System::Shutdown() {
                                 perf_stats->GetMeanFrametime());
 
     // Shutdown emulation session
-    GDBStub::Shutdown();
     VideoCore::Shutdown();
     HW::Shutdown();
+    if (!is_deserializing) {
+        GDBStub::Shutdown();
+        perf_stats.reset();
+        cheat_engine.reset();
+        app_loader.reset();
+    }
     telemetry_session.reset();
-    perf_stats.reset();
     rpc_server.reset();
-    cheat_engine.reset();
     archive_manager.reset();
     service_manager.reset();
     dsp_core.reset();
     cpu_cores.clear();
     kernel.reset();
     timing.reset();
-    app_loader.reset();
 
     if (video_dumper->IsDumping()) {
         video_dumper->StopDumping();
@@ -469,4 +550,63 @@ void System::Reset() {
     Load(*m_emu_window, m_filepath);
 }
 
+template <class Archive>
+void System::serialize(Archive& ar, const unsigned int file_version) {
+
+    u32 num_cores;
+    if (Archive::is_saving::value) {
+        num_cores = this->GetNumCores();
+    }
+    ar& num_cores;
+
+    if (Archive::is_loading::value) {
+        // When loading, we want to make sure any lingering state gets cleared out before we begin.
+        // Shutdown, but persist a few things between loads...
+        Shutdown(true);
+
+        // Re-initialize everything like it was before
+        auto system_mode = this->app_loader->LoadKernelSystemMode();
+        auto n3ds_mode = this->app_loader->LoadKernelN3dsMode();
+        Init(*m_emu_window, *system_mode.first, *n3ds_mode.first, num_cores);
+    }
+
+    // flush on save, don't flush on load
+    bool should_flush = !Archive::is_loading::value;
+    Memory::RasterizerClearAll(should_flush);
+    ar&* timing.get();
+    for (u32 i = 0; i < num_cores; i++) {
+        ar&* cpu_cores[i].get();
+    }
+    ar&* service_manager.get();
+    ar&* archive_manager.get();
+    ar& GPU::g_regs;
+    ar& LCD::g_regs;
+
+    // NOTE: DSP doesn't like being destroyed and recreated. So instead we do an inline
+    // serialization; this means that the DSP Settings need to match for loading to work.
+    auto dsp_hle = dynamic_cast<AudioCore::DspHle*>(dsp_core.get());
+    if (dsp_hle) {
+        ar&* dsp_hle;
+    } else {
+        throw std::runtime_error("LLE audio not supported for save states");
+    }
+
+    ar&* memory.get();
+    ar&* kernel.get();
+    VideoCore::serialize(ar, file_version);
+    if (file_version >= 1) {
+        ar& Movie::GetInstance();
+    }
+
+    // This needs to be set from somewhere - might as well be here!
+    if (Archive::is_loading::value) {
+        Service::GSP::SetGlobalModule(*this);
+        memory->SetDSP(*dsp_core);
+        cheat_engine->Connect();
+        VideoCore::g_renderer->Sync();
+    }
+}
+
+SERIALIZE_IMPL(System)
+
 } // namespace Core
diff --git a/src/core/core.h b/src/core/core.h
index 4bc8e6a85..e7fbd8ee3 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -5,7 +5,9 @@
 #pragma once
 
 #include <memory>
+#include <mutex>
 #include <string>
+#include <boost/serialization/version.hpp>
 #include "common/common_types.h"
 #include "core/custom_tex_cache.h"
 #include "core/frontend/applets/mii_selector.h"
@@ -87,10 +89,13 @@ public:
                                             /// generic drivers installed
         ErrorVideoCore_ErrorBelowGL33,      ///< Error in the video core due to the user not having
                                             /// OpenGL 3.3 or higher
+        ErrorSavestate,                     ///< Error saving or loading
         ShutdownRequested,                  ///< Emulated program requested a system shutdown
         ErrorUnknown                        ///< Any other error
     };
 
+    ~System();
+
     /**
      * Run the core CPU loop
      * This function runs the core for the specified number of CPU instructions before trying to
@@ -110,19 +115,23 @@ public:
     ResultStatus SingleStep();
 
     /// Shutdown the emulated system.
-    void Shutdown();
+    void Shutdown(bool is_deserializing = false);
 
     /// Shutdown and then load again
     void Reset();
 
+    enum class Signal : u32 { None, Shutdown, Reset, Save, Load };
+
+    bool SendSignal(Signal signal, u32 param = 0);
+
     /// Request reset of the system
     void RequestReset() {
-        reset_requested = true;
+        SendSignal(Signal::Reset);
     }
 
     /// Request shutdown of the system
     void RequestShutdown() {
-        shutdown_requested = true;
+        SendSignal(Signal::Shutdown);
     }
 
     /**
@@ -179,7 +188,7 @@ public:
     };
 
     u32 GetNumCores() const {
-        return cpu_cores.size();
+        return static_cast<u32>(cpu_cores.size());
     }
 
     void InvalidateCacheRange(u32 start_address, std::size_t length) {
@@ -295,6 +304,10 @@ public:
         return registered_image_interface;
     }
 
+    void SaveState(u32 slot) const;
+
+    void LoadState(u32 slot);
+
 private:
     /**
      * Initialize the emulated system.
@@ -303,7 +316,8 @@ private:
      * @param system_mode The system mode.
      * @return ResultStatus code, indicating if the operation succeeded.
      */
-    ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode);
+    ResultStatus Init(Frontend::EmuWindow& emu_window, u32 system_mode, u8 n3ds_mode,
+                      u32 num_cores);
 
     /// Reschedule the core emulation
     void Reschedule();
@@ -325,7 +339,7 @@ private:
     std::unique_ptr<Core::TelemetrySession> telemetry_session;
 
     /// Service manager
-    std::shared_ptr<Service::SM::ServiceManager> service_manager;
+    std::unique_ptr<Service::SM::ServiceManager> service_manager;
 
     /// Frontend applets
     std::shared_ptr<Frontend::MiiSelector> registered_mii_selector;
@@ -362,9 +376,15 @@ private:
     /// Saved variables for reset
     Frontend::EmuWindow* m_emu_window;
     std::string m_filepath;
+    u64 title_id;
 
-    std::atomic<bool> reset_requested;
-    std::atomic<bool> shutdown_requested;
+    std::mutex signal_mutex;
+    Signal current_signal;
+    u32 signal_param;
+
+    friend class boost::serialization::access;
+    template <typename Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 
 inline ARM_Interface& GetRunningCore() {
@@ -384,3 +404,5 @@ inline AudioCore::DspInterface& DSP() {
 }
 
 } // namespace Core
+
+BOOST_CLASS_VERSION(Core::System, 1)
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 5dbd5f74c..963926596 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -23,8 +23,9 @@ bool Timing::Event::operator<(const Timing::Event& right) const {
 Timing::Timing(std::size_t num_cores, u32 cpu_clock_percentage) {
     timers.resize(num_cores);
     for (std::size_t i = 0; i < num_cores; ++i) {
-        timers[i] = std::make_shared<Timer>(100.0 / cpu_clock_percentage);
+        timers[i] = std::make_shared<Timer>();
     }
+    UpdateClockSpeed(cpu_clock_percentage);
     current_timer = timers[0];
 }
 
@@ -37,14 +38,12 @@ void Timing::UpdateClockSpeed(u32 cpu_clock_percentage) {
 TimingEventType* Timing::RegisterEvent(const std::string& name, TimedCallback callback) {
     // check for existing type with same name.
     // we want event type names to remain unique so that we can use them for serialization.
-    ASSERT_MSG(event_types.find(name) == event_types.end(),
-               "CoreTiming Event \"{}\" is already registered. Events should only be registered "
-               "during Init to avoid breaking save states.",
-               name);
-
-    auto info = event_types.emplace(name, TimingEventType{callback, nullptr});
+    auto info = event_types.emplace(name, TimingEventType{});
     TimingEventType* event_type = &info.first->second;
     event_type->name = &info.first->first;
+    if (callback != nullptr) {
+        event_type->callback = callback;
+    }
     return event_type;
 }
 
@@ -123,7 +122,7 @@ std::shared_ptr<Timing::Timer> Timing::GetTimer(std::size_t cpu_id) {
     return timers[cpu_id];
 }
 
-Timing::Timer::Timer(double cpu_clock_scale_) : cpu_clock_scale(cpu_clock_scale_) {}
+Timing::Timer::Timer() = default;
 
 Timing::Timer::~Timer() {
     MoveEvents();
@@ -184,7 +183,11 @@ void Timing::Timer::Advance(s64 max_slice_length) {
         Event evt = std::move(event_queue.front());
         std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
         event_queue.pop_back();
-        evt.type->callback(evt.userdata, executed_ticks - evt.time);
+        if (evt.type->callback != nullptr) {
+            evt.type->callback(evt.userdata, executed_ticks - evt.time);
+        } else {
+            LOG_ERROR(Core, "Event '{}' has no callback", *evt.type->name);
+        }
     }
 
     is_timer_sane = false;
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 929f39865..bb34c79b0 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -23,9 +23,12 @@
 #include <string>
 #include <unordered_map>
 #include <vector>
+#include <boost/serialization/split_member.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/threadsafe_queue.h"
+#include "core/global.h"
 
 // The timing we get from the assembly is 268,111,855.956 Hz
 // It is possible that this number isn't just an integer because the compiler could have
@@ -133,6 +136,7 @@ struct TimingEventType {
 };
 
 class Timing {
+
 public:
     struct Event {
         s64 time;
@@ -142,13 +146,36 @@ public:
 
         bool operator>(const Event& right) const;
         bool operator<(const Event& right) const;
+
+    private:
+        template <class Archive>
+        void save(Archive& ar, const unsigned int) const {
+            ar& time;
+            ar& fifo_order;
+            ar& userdata;
+            std::string name = *(type->name);
+            ar << name;
+        }
+
+        template <class Archive>
+        void load(Archive& ar, const unsigned int) {
+            ar& time;
+            ar& fifo_order;
+            ar& userdata;
+            std::string name;
+            ar >> name;
+            type = Global<Timing>().RegisterEvent(name, nullptr);
+        }
+        friend class boost::serialization::access;
+
+        BOOST_SERIALIZATION_SPLIT_MEMBER()
     };
 
     static constexpr int MAX_SLICE_LENGTH = 20000;
 
     class Timer {
     public:
-        Timer(double cpu_clock_scale);
+        Timer();
         ~Timer();
 
         s64 GetMaxSliceLength() const;
@@ -195,6 +222,19 @@ public:
         // Stores a scaling for the internal clockspeed. Changing this number results in
         // under/overclocking the guest cpu
         double cpu_clock_scale = 1.0;
+
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            MoveEvents();
+            // NOTE: ts_queue should be empty now
+            ar& event_queue;
+            ar& event_fifo_id;
+            ar& slice_length;
+            ar& downcount;
+            ar& executed_ticks;
+            ar& idled_cycles;
+        }
+        friend class boost::serialization::access;
     };
 
     explicit Timing(std::size_t num_cores, u32 cpu_clock_percentage);
@@ -246,6 +286,15 @@ private:
     // Stores a scaling for the internal clockspeed. Changing this number results in
     // under/overclocking the guest cpu
     double cpu_clock_scale = 1.0;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        // event_types set during initialization of other things
+        ar& global_timer;
+        ar& timers;
+        ar& current_timer;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Core
diff --git a/src/core/file_sys/archive_backend.h b/src/core/file_sys/archive_backend.h
index 2df4f98c4..0c5b14dc5 100644
--- a/src/core/file_sys/archive_backend.h
+++ b/src/core/file_sys/archive_backend.h
@@ -8,6 +8,8 @@
 #include <string>
 #include <utility>
 #include <vector>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/swap.h"
@@ -64,6 +66,32 @@ private:
     std::vector<u8> binary;
     std::string string;
     std::u16string u16str;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& type;
+        switch (type) {
+        case LowPathType::Binary:
+            ar& binary;
+            break;
+        case LowPathType::Char:
+            ar& string;
+            break;
+        case LowPathType::Wchar: {
+            std::vector<char16_t> data;
+            if (Archive::is_saving::value) {
+                std::copy(u16str.begin(), u16str.end(), std::back_inserter(data));
+            }
+            ar& data;
+            if (Archive::is_loading::value) {
+                u16str = std::u16string(data.data(), data.size());
+            }
+        } break;
+        default:
+            break;
+        }
+    }
+    friend class boost::serialization::access;
 };
 
 /// Parameters of the archive, as specified in the Create or Format call.
@@ -169,6 +197,13 @@ public:
 
 protected:
     std::unique_ptr<DelayGenerator> delay_generator;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& delay_generator;
+    }
+    friend class boost::serialization::access;
 };
 
 class ArchiveFactory : NonCopyable {
@@ -205,6 +240,10 @@ public:
      * @return Format information about the archive or error code
      */
     virtual ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const = 0;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {}
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp
index efcdb2d7f..8326122f5 100644
--- a/src/core/file_sys/archive_extsavedata.cpp
+++ b/src/core/file_sys/archive_extsavedata.cpp
@@ -6,6 +6,7 @@
 #include <memory>
 #include <vector>
 #include <fmt/format.h>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -19,6 +20,8 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_ExtSaveData)
+
 namespace FileSys {
 
 /**
@@ -77,6 +80,8 @@ public:
         static constexpr u64 IPCDelayNanoseconds(3085068);
         return IPCDelayNanoseconds;
     }
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 /**
@@ -162,6 +167,14 @@ public:
         }
         return SaveDataArchive::CreateFile(path, size);
     }
+
+private:
+    ExtSaveDataArchive() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<SaveDataArchive>(*this);
+    }
+    friend class boost::serialization::access;
 };
 
 struct ExtSaveDataArchivePath {
@@ -297,3 +310,6 @@ void ArchiveFactory_ExtSaveData::WriteIcon(const Path& path, const u8* icon_data
 }
 
 } // namespace FileSys
+
+SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataDelayGenerator)
+SERIALIZE_EXPORT_IMPL(FileSys::ExtSaveDataArchive)
diff --git a/src/core/file_sys/archive_extsavedata.h b/src/core/file_sys/archive_extsavedata.h
index 7dc345c84..f33dd1a89 100644
--- a/src/core/file_sys/archive_extsavedata.h
+++ b/src/core/file_sys/archive_extsavedata.h
@@ -6,6 +6,8 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "core/file_sys/archive_backend.h"
 #include "core/hle/result.h"
@@ -54,6 +56,15 @@ private:
 
     /// Returns a path with the correct SaveIdHigh value for Shared extdata paths.
     Path GetCorrectedPath(const Path& path);
+
+    ArchiveFactory_ExtSaveData() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& shared;
+        ar& mount_point;
+    }
+    friend class boost::serialization::access;
 };
 
 /**
@@ -93,4 +104,9 @@ std::string GetExtDataContainerPath(const std::string& mount_point, bool shared)
  */
 Path ConstructExtDataBinaryPath(u32 media_type, u32 high, u32 low);
 
+class ExtSaveDataDelayGenerator;
+
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_ExtSaveData)
+BOOST_CLASS_EXPORT_KEY(FileSys::ExtSaveDataDelayGenerator)
diff --git a/src/core/file_sys/archive_ncch.cpp b/src/core/file_sys/archive_ncch.cpp
index 06fe69622..789547f0e 100644
--- a/src/core/file_sys/archive_ncch.cpp
+++ b/src/core/file_sys/archive_ncch.cpp
@@ -8,6 +8,7 @@
 #include <utility>
 #include <vector>
 #include "bad_word_list.app.romfs.h"
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -28,6 +29,10 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::NCCHArchive)
+SERIALIZE_EXPORT_IMPL(FileSys::NCCHFile)
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_NCCH)
+
 namespace FileSys {
 
 struct NCCHArchivePath {
diff --git a/src/core/file_sys/archive_ncch.h b/src/core/file_sys/archive_ncch.h
index 28d9ff044..95fe889ea 100644
--- a/src/core/file_sys/archive_ncch.h
+++ b/src/core/file_sys/archive_ncch.h
@@ -7,6 +7,9 @@
 #include <array>
 #include <memory>
 #include <string>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/vector.hpp>
 #include "core/file_sys/archive_backend.h"
 #include "core/file_sys/file_backend.h"
 #include "core/hle/result.h"
@@ -63,6 +66,17 @@ public:
 protected:
     u64 title_id;
     Service::FS::MediaType media_type;
+
+private:
+    NCCHArchive() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveBackend>(*this);
+        ar& title_id;
+        ar& media_type;
+    }
+    friend class boost::serialization::access;
 };
 
 // File backend for NCCH files
@@ -82,6 +96,15 @@ public:
 
 private:
     std::vector<u8> file_buffer;
+
+    NCCHFile() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<FileBackend>(*this);
+        ar& file_buffer;
+    }
+    friend class boost::serialization::access;
 };
 
 /// File system interface to the NCCH archive
@@ -97,6 +120,17 @@ public:
     ResultCode Format(const Path& path, const FileSys::ArchiveFormatInfo& format_info,
                       u64 program_id) override;
     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::NCCHArchive)
+BOOST_CLASS_EXPORT_KEY(FileSys::NCCHFile)
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_NCCH)
diff --git a/src/core/file_sys/archive_other_savedata.cpp b/src/core/file_sys/archive_other_savedata.cpp
index 1c3b071ba..d4f8debc4 100644
--- a/src/core/file_sys/archive_other_savedata.cpp
+++ b/src/core/file_sys/archive_other_savedata.cpp
@@ -4,6 +4,7 @@
 
 #include <tuple>
 #include <utility>
+#include "common/archives.h"
 #include "core/file_sys/archive_other_savedata.h"
 #include "core/file_sys/errors.h"
 #include "core/hle/kernel/process.h"
@@ -12,6 +13,9 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_OtherSaveDataPermitted)
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_OtherSaveDataGeneral)
+
 namespace FileSys {
 
 // TODO(wwylele): The storage info in exheader should be checked before accessing these archives
diff --git a/src/core/file_sys/archive_other_savedata.h b/src/core/file_sys/archive_other_savedata.h
index e3e8f83c3..f002fec7f 100644
--- a/src/core/file_sys/archive_other_savedata.h
+++ b/src/core/file_sys/archive_other_savedata.h
@@ -4,6 +4,9 @@
 
 #pragma once
 
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "core/file_sys/archive_source_sd_savedata.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -27,8 +30,15 @@ public:
     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
 
 private:
-    std::string mount_point;
     std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
+
+    ArchiveFactory_OtherSaveDataPermitted() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& sd_savedata_source;
+    }
+    friend class boost::serialization::access;
 };
 
 /// File system interface to the OtherSaveDataGeneral archive
@@ -47,8 +57,18 @@ public:
     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
 
 private:
-    std::string mount_point;
     std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
+
+    ArchiveFactory_OtherSaveDataGeneral() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& sd_savedata_source;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_OtherSaveDataPermitted)
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_OtherSaveDataGeneral)
diff --git a/src/core/file_sys/archive_savedata.cpp b/src/core/file_sys/archive_savedata.cpp
index c1046e2f5..fc4bd34fa 100644
--- a/src/core/file_sys/archive_savedata.cpp
+++ b/src/core/file_sys/archive_savedata.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <utility>
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/file_sys/archive_savedata.h"
 #include "core/hle/kernel/process.h"
@@ -10,6 +11,8 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SaveData)
+
 namespace FileSys {
 
 ArchiveFactory_SaveData::ArchiveFactory_SaveData(
diff --git a/src/core/file_sys/archive_savedata.h b/src/core/file_sys/archive_savedata.h
index 591a3b900..5bc4b8ecd 100644
--- a/src/core/file_sys/archive_savedata.h
+++ b/src/core/file_sys/archive_savedata.h
@@ -4,6 +4,8 @@
 
 #pragma once
 
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "core/file_sys/archive_source_sd_savedata.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -27,8 +29,17 @@ public:
     ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path, u64 program_id) const override;
 
 private:
-    std::string mount_point;
     std::shared_ptr<ArchiveSource_SDSaveData> sd_savedata_source;
+
+    ArchiveFactory_SaveData() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& sd_savedata_source;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SaveData)
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp
index d2269fe7c..4c3ce6d69 100644
--- a/src/core/file_sys/archive_sdmc.cpp
+++ b/src/core/file_sys/archive_sdmc.cpp
@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <memory>
+#include "common/archives.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "core/file_sys/archive_sdmc.h"
@@ -15,6 +16,9 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::SDMCArchive)
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SDMC)
+
 namespace FileSys {
 
 class SDMCDelayGenerator : public DelayGenerator {
@@ -37,6 +41,8 @@ public:
         static constexpr u64 IPCDelayNanoseconds(269082);
         return IPCDelayNanoseconds;
     }
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFile(const Path& path,
@@ -405,3 +411,5 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMC::GetFormatInfo(const Path& path
     return ResultCode(-1);
 }
 } // namespace FileSys
+
+SERIALIZE_EXPORT_IMPL(FileSys::SDMCDelayGenerator)
diff --git a/src/core/file_sys/archive_sdmc.h b/src/core/file_sys/archive_sdmc.h
index 41d7b7c59..265dd7e93 100644
--- a/src/core/file_sys/archive_sdmc.h
+++ b/src/core/file_sys/archive_sdmc.h
@@ -6,6 +6,9 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/string.hpp>
 #include "core/file_sys/archive_backend.h"
 #include "core/hle/result.h"
 
@@ -42,6 +45,14 @@ public:
 protected:
     ResultVal<std::unique_ptr<FileBackend>> OpenFileBase(const Path& path, const Mode& mode) const;
     std::string mount_point;
+
+    SDMCArchive() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveBackend>(*this);
+        ar& mount_point;
+    }
+    friend class boost::serialization::access;
 };
 
 /// File system interface to the SDMC archive
@@ -66,6 +77,20 @@ public:
 
 private:
     std::string sdmc_directory;
+
+    ArchiveFactory_SDMC() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& sdmc_directory;
+    }
+    friend class boost::serialization::access;
 };
 
+class SDMCDelayGenerator;
+
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::SDMCArchive)
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SDMC)
+BOOST_CLASS_EXPORT_KEY(FileSys::SDMCDelayGenerator)
diff --git a/src/core/file_sys/archive_sdmcwriteonly.cpp b/src/core/file_sys/archive_sdmcwriteonly.cpp
index 74552d751..241e93f0a 100644
--- a/src/core/file_sys/archive_sdmcwriteonly.cpp
+++ b/src/core/file_sys/archive_sdmcwriteonly.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <memory>
+#include "common/archives.h"
 #include "common/file_util.h"
 #include "core/file_sys/archive_sdmcwriteonly.h"
 #include "core/file_sys/directory_backend.h"
@@ -13,6 +14,9 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::SDMCWriteOnlyArchive)
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SDMCWriteOnly)
+
 namespace FileSys {
 
 class SDMCWriteOnlyDelayGenerator : public DelayGenerator {
@@ -35,6 +39,8 @@ public:
         static constexpr u64 IPCDelayNanoseconds(269082);
         return IPCDelayNanoseconds;
     }
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 ResultVal<std::unique_ptr<FileBackend>> SDMCWriteOnlyArchive::OpenFile(const Path& path,
@@ -96,3 +102,5 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_SDMCWriteOnly::GetFormatInfo(const P
 }
 
 } // namespace FileSys
+
+SERIALIZE_EXPORT_IMPL(FileSys::SDMCWriteOnlyDelayGenerator)
diff --git a/src/core/file_sys/archive_sdmcwriteonly.h b/src/core/file_sys/archive_sdmcwriteonly.h
index 8191f053f..f4149961c 100644
--- a/src/core/file_sys/archive_sdmcwriteonly.h
+++ b/src/core/file_sys/archive_sdmcwriteonly.h
@@ -31,6 +31,14 @@ public:
                                                      const Mode& mode) const override;
 
     ResultVal<std::unique_ptr<DirectoryBackend>> OpenDirectory(const Path& path) const override;
+
+private:
+    SDMCWriteOnlyArchive() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<SDMCArchive>(*this);
+    }
+    friend class boost::serialization::access;
 };
 
 /// File system interface to the SDMC write-only archive
@@ -55,6 +63,20 @@ public:
 
 private:
     std::string sdmc_directory;
+
+    ArchiveFactory_SDMCWriteOnly() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& sdmc_directory;
+    }
+    friend class boost::serialization::access;
 };
 
+class SDMCWriteOnlyDelayGenerator;
+
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::SDMCWriteOnlyArchive)
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SDMCWriteOnly)
+BOOST_CLASS_EXPORT_KEY(FileSys::SDMCWriteOnlyDelayGenerator)
diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp
index 0f8dd8065..e866e9bc0 100644
--- a/src/core/file_sys/archive_selfncch.cpp
+++ b/src/core/file_sys/archive_selfncch.cpp
@@ -4,6 +4,7 @@
 
 #include <array>
 #include <cinttypes>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/swap.h"
@@ -16,6 +17,8 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SelfNCCH)
+
 namespace FileSys {
 
 enum class SelfNCCHFilePathType : u32 {
@@ -74,6 +77,15 @@ public:
 
 private:
     std::shared_ptr<std::vector<u8>> data;
+
+    ExeFSSectionFile() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<FileBackend>(*this);
+        ar& data;
+    }
+    friend class boost::serialization::access;
 };
 
 // SelfNCCHArchive represents the running application itself. From this archive the application can
@@ -231,6 +243,15 @@ private:
     }
 
     NCCHData ncch_data;
+
+    SelfNCCHArchive() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveBackend>(*this);
+        ar& ncch_data;
+    }
+    friend class boost::serialization::access;
 };
 
 void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) {
@@ -297,3 +318,6 @@ ResultVal<ArchiveFormatInfo> ArchiveFactory_SelfNCCH::GetFormatInfo(const Path&,
 }
 
 } // namespace FileSys
+
+SERIALIZE_EXPORT_IMPL(FileSys::ExeFSSectionFile)
+SERIALIZE_EXPORT_IMPL(FileSys::SelfNCCHArchive)
diff --git a/src/core/file_sys/archive_selfncch.h b/src/core/file_sys/archive_selfncch.h
index 779e7e75a..9f5b353b3 100644
--- a/src/core/file_sys/archive_selfncch.h
+++ b/src/core/file_sys/archive_selfncch.h
@@ -8,6 +8,10 @@
 #include <string>
 #include <unordered_map>
 #include <vector>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unordered_map.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
 #include "core/file_sys/archive_backend.h"
 #include "core/hle/result.h"
@@ -24,6 +28,17 @@ struct NCCHData {
     std::shared_ptr<std::vector<u8>> banner;
     std::shared_ptr<RomFSReader> romfs_file;
     std::shared_ptr<RomFSReader> update_romfs_file;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& icon;
+        ar& logo;
+        ar& banner;
+        ar& romfs_file;
+        ar& update_romfs_file;
+    }
+    friend class boost::serialization::access;
 };
 
 /// File system interface to the SelfNCCH archive
@@ -45,6 +60,20 @@ public:
 private:
     /// Mapping of ProgramId -> NCCHData
     std::unordered_map<u64, NCCHData> ncch_data;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& ncch_data;
+    }
+    friend class boost::serialization::access;
 };
 
+class ExeFSSectionFile;
+class SelfNCCHArchive;
+
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SelfNCCH)
+BOOST_CLASS_EXPORT_KEY(FileSys::ExeFSSectionFile)
+BOOST_CLASS_EXPORT_KEY(FileSys::SelfNCCHArchive)
diff --git a/src/core/file_sys/archive_source_sd_savedata.cpp b/src/core/file_sys/archive_source_sd_savedata.cpp
index 0b8072b96..9afbfd73c 100644
--- a/src/core/file_sys/archive_source_sd_savedata.cpp
+++ b/src/core/file_sys/archive_source_sd_savedata.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <fmt/format.h>
+#include "common/archives.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
 #include "core/file_sys/archive_source_sd_savedata.h"
@@ -13,6 +14,8 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveSource_SDSaveData)
+
 namespace FileSys {
 
 namespace {
diff --git a/src/core/file_sys/archive_source_sd_savedata.h b/src/core/file_sys/archive_source_sd_savedata.h
index b5fe43cc1..4ac028a3d 100644
--- a/src/core/file_sys/archive_source_sd_savedata.h
+++ b/src/core/file_sys/archive_source_sd_savedata.h
@@ -6,6 +6,8 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/string.hpp>
 #include "core/file_sys/archive_backend.h"
 #include "core/hle/result.h"
 
@@ -27,6 +29,15 @@ public:
 
 private:
     std::string mount_point;
+
+    ArchiveSource_SDSaveData() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& mount_point;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveSource_SDSaveData)
diff --git a/src/core/file_sys/archive_systemsavedata.cpp b/src/core/file_sys/archive_systemsavedata.cpp
index cef42e561..ecfb34219 100644
--- a/src/core/file_sys/archive_systemsavedata.cpp
+++ b/src/core/file_sys/archive_systemsavedata.cpp
@@ -7,6 +7,7 @@
 #include <memory>
 #include <vector>
 #include <fmt/format.h>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "core/file_sys/archive_systemsavedata.h"
@@ -17,6 +18,8 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::ArchiveFactory_SystemSaveData)
+
 namespace FileSys {
 
 std::string GetSystemSaveDataPath(const std::string& mount_point, const Path& path) {
diff --git a/src/core/file_sys/archive_systemsavedata.h b/src/core/file_sys/archive_systemsavedata.h
index e72ecce3a..d4f204a66 100644
--- a/src/core/file_sys/archive_systemsavedata.h
+++ b/src/core/file_sys/archive_systemsavedata.h
@@ -6,6 +6,8 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "core/file_sys/archive_backend.h"
 #include "core/hle/result.h"
@@ -31,6 +33,14 @@ public:
 
 private:
     std::string base_path;
+
+    ArchiveFactory_SystemSaveData() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveFactory>(*this);
+        ar& base_path;
+    }
+    friend class boost::serialization::access;
 };
 
 /**
@@ -60,3 +70,5 @@ std::string GetSystemSaveDataContainerPath(const std::string& mount_point);
 Path ConstructSystemSaveDataBinaryPath(u32 high, u32 low);
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::ArchiveFactory_SystemSaveData)
diff --git a/src/core/file_sys/delay_generator.cpp b/src/core/file_sys/delay_generator.cpp
index 04f877f83..137e63d69 100644
--- a/src/core/file_sys/delay_generator.cpp
+++ b/src/core/file_sys/delay_generator.cpp
@@ -3,8 +3,11 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
+#include "common/archives.h"
 #include "core/file_sys/delay_generator.h"
 
+SERIALIZE_EXPORT_IMPL(FileSys::DefaultDelayGenerator)
+
 namespace FileSys {
 
 DelayGenerator::~DelayGenerator() = default;
diff --git a/src/core/file_sys/delay_generator.h b/src/core/file_sys/delay_generator.h
index d530f2ee2..6513a730e 100644
--- a/src/core/file_sys/delay_generator.h
+++ b/src/core/file_sys/delay_generator.h
@@ -5,8 +5,18 @@
 #pragma once
 
 #include <cstddef>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
 #include "common/common_types.h"
 
+#define SERIALIZE_DELAY_GENERATOR                                                                  \
+private:                                                                                           \
+    template <class Archive>                                                                       \
+    void serialize(Archive& ar, const unsigned int) {                                              \
+        ar& boost::serialization::base_object<DelayGenerator>(*this);                              \
+    }                                                                                              \
+    friend class boost::serialization::access;
+
 namespace FileSys {
 
 class DelayGenerator {
@@ -16,12 +26,20 @@ public:
     virtual u64 GetOpenDelayNs() = 0;
 
     // TODO (B3N30): Add getter for all other file/directory io operations
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {}
+    friend class boost::serialization::access;
 };
 
 class DefaultDelayGenerator : public DelayGenerator {
 public:
     u64 GetReadDelayNs(std::size_t length) override;
     u64 GetOpenDelayNs() override;
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::DefaultDelayGenerator);
diff --git a/src/core/file_sys/directory_backend.h b/src/core/file_sys/directory_backend.h
index e9f124b02..4c9dbb4df 100644
--- a/src/core/file_sys/directory_backend.h
+++ b/src/core/file_sys/directory_backend.h
@@ -53,6 +53,11 @@ public:
      * @return true if the directory closed correctly
      */
     virtual bool Close() const = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {}
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
diff --git a/src/core/file_sys/disk_archive.cpp b/src/core/file_sys/disk_archive.cpp
index 061460546..6ace314bf 100644
--- a/src/core/file_sys/disk_archive.cpp
+++ b/src/core/file_sys/disk_archive.cpp
@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <cstdio>
 #include <memory>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -14,6 +15,9 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::DiskFile)
+SERIALIZE_EXPORT_IMPL(FileSys::DiskDirectory)
+
 namespace FileSys {
 
 ResultVal<std::size_t> DiskFile::Read(const u64 offset, const std::size_t length,
diff --git a/src/core/file_sys/disk_archive.h b/src/core/file_sys/disk_archive.h
index 481aa470a..2f0e5691a 100644
--- a/src/core/file_sys/disk_archive.h
+++ b/src/core/file_sys/disk_archive.h
@@ -8,6 +8,9 @@
 #include <memory>
 #include <string>
 #include <vector>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/unique_ptr.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "core/file_sys/archive_backend.h"
@@ -43,6 +46,17 @@ public:
 protected:
     Mode mode;
     std::unique_ptr<FileUtil::IOFile> file;
+
+private:
+    DiskFile() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<FileBackend>(*this);
+        ar& mode.hex;
+        ar& file;
+    }
+    friend class boost::serialization::access;
 };
 
 class DiskDirectory : public DirectoryBackend {
@@ -65,6 +79,27 @@ protected:
     // We need to remember the last entry we returned, so a subsequent call to Read will continue
     // from the next one.  This iterator will always point to the next unread entry.
     std::vector<FileUtil::FSTEntry>::iterator children_iterator;
+
+private:
+    DiskDirectory() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<DirectoryBackend>(*this);
+        ar& directory;
+        u64 child_index;
+        if (Archive::is_saving::value) {
+            child_index = children_iterator - directory.children.begin();
+        }
+        ar& child_index;
+        if (Archive::is_loading::value) {
+            children_iterator = directory.children.begin() + child_index;
+        }
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::DiskFile)
+BOOST_CLASS_EXPORT_KEY(FileSys::DiskDirectory)
diff --git a/src/core/file_sys/file_backend.h b/src/core/file_sys/file_backend.h
index c865c98e8..d56fc4c1c 100644
--- a/src/core/file_sys/file_backend.h
+++ b/src/core/file_sys/file_backend.h
@@ -7,6 +7,7 @@
 #include <algorithm>
 #include <cstddef>
 #include <memory>
+#include <boost/serialization/unique_ptr.hpp>
 #include "common/common_types.h"
 #include "core/hle/result.h"
 #include "delay_generator.h"
@@ -90,6 +91,12 @@ public:
 
 protected:
     std::unique_ptr<DelayGenerator> delay_generator;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& delay_generator;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
diff --git a/src/core/file_sys/ivfc_archive.cpp b/src/core/file_sys/ivfc_archive.cpp
index ba16f8cd8..10d085164 100644
--- a/src/core/file_sys/ivfc_archive.cpp
+++ b/src/core/file_sys/ivfc_archive.cpp
@@ -5,6 +5,7 @@
 #include <cstring>
 #include <memory>
 #include <utility>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/file_sys/ivfc_archive.h"
@@ -12,6 +13,12 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // FileSys namespace
 
+SERIALIZE_EXPORT_IMPL(FileSys::IVFCFile)
+SERIALIZE_EXPORT_IMPL(FileSys::IVFCFileInMemory)
+SERIALIZE_EXPORT_IMPL(FileSys::IVFCDelayGenerator)
+SERIALIZE_EXPORT_IMPL(FileSys::RomFSDelayGenerator)
+SERIALIZE_EXPORT_IMPL(FileSys::ExeFSDelayGenerator)
+
 namespace FileSys {
 
 IVFCArchive::IVFCArchive(std::shared_ptr<RomFSReader> file,
diff --git a/src/core/file_sys/ivfc_archive.h b/src/core/file_sys/ivfc_archive.h
index 8168e04f4..145fabb98 100644
--- a/src/core/file_sys/ivfc_archive.h
+++ b/src/core/file_sys/ivfc_archive.h
@@ -8,6 +8,8 @@
 #include <memory>
 #include <string>
 #include <vector>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "core/file_sys/archive_backend.h"
@@ -38,6 +40,8 @@ class IVFCDelayGenerator : public DelayGenerator {
         static constexpr u64 IPCDelayNanoseconds(9438006);
         return IPCDelayNanoseconds;
     }
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 class RomFSDelayGenerator : public DelayGenerator {
@@ -60,6 +64,8 @@ public:
         static constexpr u64 IPCDelayNanoseconds(9438006);
         return IPCDelayNanoseconds;
     }
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 class ExeFSDelayGenerator : public DelayGenerator {
@@ -82,6 +88,8 @@ public:
         static constexpr u64 IPCDelayNanoseconds(9438006);
         return IPCDelayNanoseconds;
     }
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 /**
@@ -128,6 +136,15 @@ public:
 
 private:
     std::shared_ptr<RomFSReader> romfs_file;
+
+    IVFCFile() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<FileBackend>(*this);
+        ar& romfs_file;
+    }
+    friend class boost::serialization::access;
 };
 
 class IVFCDirectory : public DirectoryBackend {
@@ -159,6 +176,23 @@ private:
     std::vector<u8> romfs_file;
     u64 data_offset;
     u64 data_size;
+
+    IVFCFileInMemory() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<FileBackend>(*this);
+        ar& romfs_file;
+        ar& data_offset;
+        ar& data_size;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::IVFCFile)
+BOOST_CLASS_EXPORT_KEY(FileSys::IVFCFileInMemory)
+BOOST_CLASS_EXPORT_KEY(FileSys::IVFCDelayGenerator)
+BOOST_CLASS_EXPORT_KEY(FileSys::RomFSDelayGenerator)
+BOOST_CLASS_EXPORT_KEY(FileSys::ExeFSDelayGenerator)
diff --git a/src/core/file_sys/layered_fs.cpp b/src/core/file_sys/layered_fs.cpp
index 9d5fbf7c2..a4ef57baf 100644
--- a/src/core/file_sys/layered_fs.cpp
+++ b/src/core/file_sys/layered_fs.cpp
@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <cstring>
 #include "common/alignment.h"
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/common_paths.h"
 #include "common/file_util.h"
@@ -13,6 +14,8 @@
 #include "core/file_sys/layered_fs.h"
 #include "core/file_sys/patch.h"
 
+SERIALIZE_EXPORT_IMPL(FileSys::LayeredFS)
+
 namespace FileSys {
 
 struct FileRelocationInfo {
@@ -51,11 +54,16 @@ struct FileMetadata {
 };
 static_assert(sizeof(FileMetadata) == 0x20, "Size of FileMetadata is not correct");
 
-LayeredFS::LayeredFS(std::shared_ptr<RomFSReader> romfs_, std::string patch_path_,
-                     std::string patch_ext_path_, bool load_relocations)
-    : romfs(std::move(romfs_)), patch_path(std::move(patch_path_)),
-      patch_ext_path(std::move(patch_ext_path_)) {
+LayeredFS::LayeredFS() = default;
 
+LayeredFS::LayeredFS(std::shared_ptr<RomFSReader> romfs_, std::string patch_path_,
+                     std::string patch_ext_path_, bool load_relocations_)
+    : romfs(std::move(romfs_)), patch_path(std::move(patch_path_)),
+      patch_ext_path(std::move(patch_ext_path_)), load_relocations(load_relocations_) {
+    Load();
+}
+
+void LayeredFS::Load() {
     romfs->ReadFile(0, sizeof(header), reinterpret_cast<u8*>(&header));
 
     ASSERT_MSG(header.header_length == sizeof(header), "Header size is incorrect");
@@ -273,7 +281,7 @@ std::size_t GetNameSize(const std::string& name) {
 }
 
 void LayeredFS::PrepareBuildDirectory(Directory& current) {
-    directory_metadata_offset_map.emplace(&current, current_directory_offset);
+    directory_metadata_offset_map.emplace(&current, static_cast<u32>(current_directory_offset));
     directory_list.emplace_back(&current);
     current_directory_offset += sizeof(DirectoryMetadata) + GetNameSize(current.name);
 }
@@ -282,7 +290,7 @@ void LayeredFS::PrepareBuildFile(File& current) {
     if (current.relocation.type == 3) { // Deleted files are not counted
         return;
     }
-    file_metadata_offset_map.emplace(&current, current_file_offset);
+    file_metadata_offset_map.emplace(&current, static_cast<u32>(current_file_offset));
     file_list.emplace_back(&current);
     current_file_offset += sizeof(FileMetadata) + GetNameSize(current.name);
 }
@@ -361,7 +369,7 @@ void LayeredFS::BuildDirectories() {
 
         // Write metadata and name
         std::u16string u16name = Common::UTF8ToUTF16(directory->name);
-        metadata.name_length = u16name.size() * 2;
+        metadata.name_length = static_cast<u32_le>(u16name.size() * 2);
 
         std::memcpy(directory_metadata_table.data() + written, &metadata, sizeof(metadata));
         written += sizeof(metadata);
@@ -410,7 +418,7 @@ void LayeredFS::BuildFiles() {
 
         // Write metadata and name
         std::u16string u16name = Common::UTF8ToUTF16(file->name);
-        metadata.name_length = u16name.size() * 2;
+        metadata.name_length = static_cast<u32_le>(u16name.size() * 2);
 
         std::memcpy(file_metadata_table.data() + written, &metadata, sizeof(metadata));
         written += sizeof(metadata);
diff --git a/src/core/file_sys/layered_fs.h b/src/core/file_sys/layered_fs.h
index 956eedcfa..2a494bd8e 100644
--- a/src/core/file_sys/layered_fs.h
+++ b/src/core/file_sys/layered_fs.h
@@ -9,6 +9,10 @@
 #include <string>
 #include <unordered_map>
 #include <vector>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/file_sys/romfs_reader.h"
@@ -92,9 +96,12 @@ private:
 
     void RebuildMetadata();
 
+    void Load();
+
     std::shared_ptr<RomFSReader> romfs;
     std::string patch_path;
     std::string patch_ext_path;
+    bool load_relocations;
 
     RomFSHeader header;
     Directory root;
@@ -118,6 +125,24 @@ private:
     u64 current_file_offset{};           // current file metadata offset
     std::vector<u8> file_metadata_table; // rebuilt file metadata table
     u64 current_data_offset{};           // current assigned data offset
+
+    LayeredFS();
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<RomFSReader>(*this);
+        ar& romfs;
+        ar& patch_path;
+        ar& patch_ext_path;
+        ar& load_relocations;
+        if (Archive::is_loading::value) {
+            Load();
+        }
+        // NOTE: Everything else is essentially cached, updated when we call Load
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::LayeredFS)
diff --git a/src/core/file_sys/romfs_reader.cpp b/src/core/file_sys/romfs_reader.cpp
index 64374684a..4c83515b3 100644
--- a/src/core/file_sys/romfs_reader.cpp
+++ b/src/core/file_sys/romfs_reader.cpp
@@ -1,15 +1,18 @@
 #include <algorithm>
 #include <cryptopp/aes.h>
 #include <cryptopp/modes.h>
+#include "common/archives.h"
 #include "core/file_sys/romfs_reader.h"
 
+SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader)
+
 namespace FileSys {
 
 std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
     if (length == 0)
         return 0; // Crypto++ does not like zero size buffer
     file.Seek(file_offset + offset, SEEK_SET);
-    std::size_t read_length = std::min(length, data_size - offset);
+    std::size_t read_length = std::min(length, static_cast<std::size_t>(data_size) - offset);
     read_length = file.ReadBytes(buffer, read_length);
     if (is_encrypted) {
         CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
diff --git a/src/core/file_sys/romfs_reader.h b/src/core/file_sys/romfs_reader.h
index df0318c99..26dcb9857 100644
--- a/src/core/file_sys/romfs_reader.h
+++ b/src/core/file_sys/romfs_reader.h
@@ -1,6 +1,9 @@
 #pragma once
 
 #include <array>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
 #include "common/common_types.h"
 #include "common/file_util.h"
 
@@ -15,6 +18,11 @@ public:
 
     virtual std::size_t GetSize() const = 0;
     virtual std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {}
+    friend class boost::serialization::access;
 };
 
 /**
@@ -45,9 +53,26 @@ private:
     FileUtil::IOFile file;
     std::array<u8, 16> key;
     std::array<u8, 16> ctr;
-    std::size_t file_offset;
-    std::size_t crypto_offset;
-    std::size_t data_size;
+    u64 file_offset;
+    u64 crypto_offset;
+    u64 data_size;
+
+    DirectRomFSReader() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<RomFSReader>(*this);
+        ar& is_encrypted;
+        ar& file;
+        ar& key;
+        ar& ctr;
+        ar& file_offset;
+        ar& crypto_offset;
+        ar& data_size;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::DirectRomFSReader)
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
index 8d7830468..090dc6fb0 100644
--- a/src/core/file_sys/savedata_archive.cpp
+++ b/src/core/file_sys/savedata_archive.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "common/file_util.h"
 #include "core/file_sys/disk_archive.h"
 #include "core/file_sys/errors.h"
@@ -33,6 +34,8 @@ public:
         static constexpr u64 IPCDelayNanoseconds(269082);
         return IPCDelayNanoseconds;
     }
+
+    SERIALIZE_DELAY_GENERATOR
 };
 
 ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& path,
@@ -353,3 +356,6 @@ u64 SaveDataArchive::GetFreeBytes() const {
 }
 
 } // namespace FileSys
+
+SERIALIZE_EXPORT_IMPL(FileSys::SaveDataArchive)
+SERIALIZE_EXPORT_IMPL(FileSys::SaveDataDelayGenerator)
diff --git a/src/core/file_sys/savedata_archive.h b/src/core/file_sys/savedata_archive.h
index 176d35710..38b9653d7 100644
--- a/src/core/file_sys/savedata_archive.h
+++ b/src/core/file_sys/savedata_archive.h
@@ -38,6 +38,22 @@ public:
 
 protected:
     std::string mount_point;
+    SaveDataArchive() = default;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<ArchiveBackend>(*this);
+        ar& mount_point;
+    }
+    friend class boost::serialization::access;
 };
 
+class SaveDataDelayGenerator;
+class ExtSaveDataArchive;
+
 } // namespace FileSys
+
+BOOST_CLASS_EXPORT_KEY(FileSys::SaveDataArchive)
+BOOST_CLASS_EXPORT_KEY(FileSys::SaveDataDelayGenerator)
+BOOST_CLASS_EXPORT_KEY(FileSys::ExtSaveDataArchive)
diff --git a/src/core/global.h b/src/core/global.h
new file mode 100644
index 000000000..794d71f94
--- /dev/null
+++ b/src/core/global.h
@@ -0,0 +1,21 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Core {
+
+template <class T>
+T& Global();
+
+// Declare explicit specialisation to prevent automatic instantiation
+class System;
+template <>
+System& Global();
+
+class Timing;
+template <>
+Timing& Global();
+
+} // namespace Core
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index e52c0f272..5074e352f 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -3,8 +3,10 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
+#include "core/global.h"
 #include "core/hle/kernel/address_arbiter.h"
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/kernel.h"
@@ -14,6 +16,8 @@
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Kernel namespace
 
+SERIALIZE_EXPORT_IMPL(Kernel::AddressArbiter)
+
 namespace Kernel {
 
 void AddressArbiter::WaitThread(std::shared_ptr<Thread> thread, VAddr wait_address) {
@@ -76,16 +80,18 @@ std::shared_ptr<AddressArbiter> KernelSystem::CreateAddressArbiter(std::string n
     return address_arbiter;
 }
 
+void AddressArbiter::WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
+                            std::shared_ptr<WaitObject> object) {
+    ASSERT(reason == ThreadWakeupReason::Timeout);
+    // Remove the newly-awakened thread from the Arbiter's waiting list.
+    waiting_threads.erase(std::remove(waiting_threads.begin(), waiting_threads.end(), thread),
+                          waiting_threads.end());
+};
+
 ResultCode AddressArbiter::ArbitrateAddress(std::shared_ptr<Thread> thread, ArbitrationType type,
                                             VAddr address, s32 value, u64 nanoseconds) {
 
-    auto timeout_callback = [this](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
-                                   std::shared_ptr<WaitObject> object) {
-        ASSERT(reason == ThreadWakeupReason::Timeout);
-        // Remove the newly-awakened thread from the Arbiter's waiting list.
-        waiting_threads.erase(std::remove(waiting_threads.begin(), waiting_threads.end(), thread),
-                              waiting_threads.end());
-    };
+    auto timeout_callback = std::dynamic_pointer_cast<WakeupCallback>(shared_from_this());
 
     switch (type) {
 
diff --git a/src/core/hle/kernel/address_arbiter.h b/src/core/hle/kernel/address_arbiter.h
index a4aff6a6e..c7a263d9e 100644
--- a/src/core/hle/kernel/address_arbiter.h
+++ b/src/core/hle/kernel/address_arbiter.h
@@ -6,8 +6,15 @@
 
 #include <memory>
 #include <vector>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
+#include <boost/serialization/version.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
+#include "core/hle/kernel/thread.h"
 #include "core/hle/result.h"
 
 // Address arbiters are an underlying kernel synchronization object that can be created/used via
@@ -30,7 +37,7 @@ enum class ArbitrationType : u32 {
     DecrementAndWaitIfLessThanWithTimeout,
 };
 
-class AddressArbiter final : public Object {
+class AddressArbiter final : public Object, public WakeupCallback {
 public:
     explicit AddressArbiter(KernelSystem& kernel);
     ~AddressArbiter() override;
@@ -52,6 +59,9 @@ public:
     ResultCode ArbitrateAddress(std::shared_ptr<Thread> thread, ArbitrationType type, VAddr address,
                                 s32 value, u64 nanoseconds);
 
+    void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
+                std::shared_ptr<WaitObject> object);
+
 private:
     KernelSystem& kernel;
 
@@ -67,6 +77,21 @@ private:
 
     /// Threads waiting for the address arbiter to be signaled.
     std::vector<std::shared_ptr<Thread>> waiting_threads;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<Object>(*this);
+        if (file_version > 0) {
+            ar& boost::serialization::base_object<WakeupCallback>(*this);
+        }
+        ar& name;
+        ar& waiting_threads;
+    }
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::AddressArbiter)
+BOOST_CLASS_VERSION(Kernel::AddressArbiter, 1)
+CONSTRUCT_KERNEL_OBJECT(Kernel::AddressArbiter)
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index d217dfb7c..e7e8a8014 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -2,7 +2,9 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "common/assert.h"
+#include "core/global.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/client_session.h"
 #include "core/hle/kernel/errors.h"
@@ -11,6 +13,8 @@
 #include "core/hle/kernel/server_port.h"
 #include "core/hle/kernel/server_session.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::ClientPort)
+
 namespace Kernel {
 
 ClientPort::ClientPort(KernelSystem& kernel) : Object(kernel), kernel(kernel) {}
diff --git a/src/core/hle/kernel/client_port.h b/src/core/hle/kernel/client_port.h
index 423db0172..8d0f50520 100644
--- a/src/core/hle/kernel/client_port.h
+++ b/src/core/hle/kernel/client_port.h
@@ -6,6 +6,9 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/server_port.h"
@@ -59,6 +62,20 @@ private:
     std::string name;        ///< Name of client port (optional)
 
     friend class KernelSystem;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<Object>(*this);
+        ar& server_port;
+        ar& max_sessions;
+        ar& active_sessions;
+        ar& name;
+    }
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::ClientPort)
+CONSTRUCT_KERNEL_OBJECT(Kernel::ClientPort)
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 3e76f1a4e..a47e6411b 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -2,8 +2,8 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "common/assert.h"
-
 #include "core/hle/kernel/client_session.h"
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/hle_ipc.h"
@@ -11,6 +11,8 @@
 #include "core/hle/kernel/session.h"
 #include "core/hle/kernel/thread.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::ClientSession)
+
 namespace Kernel {
 
 ClientSession::ClientSession(KernelSystem& kernel) : Object(kernel) {}
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index de2c7b0ba..1943db8e6 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -6,6 +6,10 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/result.h"
@@ -46,6 +50,18 @@ public:
 
     /// The parent session, which links to the server endpoint.
     std::shared_ptr<Session> parent;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<Object>(*this);
+        ar& name;
+        ar& parent;
+    }
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::ClientSession)
+CONSTRUCT_KERNEL_OBJECT(Kernel::ClientSession)
diff --git a/src/core/hle/kernel/config_mem.cpp b/src/core/hle/kernel/config_mem.cpp
index 58bef4110..4b262b501 100644
--- a/src/core/hle/kernel/config_mem.cpp
+++ b/src/core/hle/kernel/config_mem.cpp
@@ -3,10 +3,13 @@
 // Refer to the license.txt file included.
 
 #include <cstring>
+#include "common/archives.h"
 #include "core/hle/kernel/config_mem.h"
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
+SERIALIZE_EXPORT_IMPL(ConfigMem::Handler)
+
 namespace ConfigMem {
 
 Handler::Handler() {
diff --git a/src/core/hle/kernel/config_mem.h b/src/core/hle/kernel/config_mem.h
index ecb97c6bd..b592aff7a 100644
--- a/src/core/hle/kernel/config_mem.h
+++ b/src/core/hle/kernel/config_mem.h
@@ -9,8 +9,11 @@
 // bootrom. Because we're not emulating this, and essentially just "stubbing" the functionality, I'm
 // putting this as a subset of HLE for now.
 
+#include <boost/serialization/binary_object.hpp>
+#include <boost/serialization/export.hpp>
 #include "common/common_funcs.h"
 #include "common/common_types.h"
+#include "common/memory_ref.h"
 #include "common/swap.h"
 #include "core/memory.h"
 
@@ -49,13 +52,34 @@ struct ConfigMemDef {
 static_assert(sizeof(ConfigMemDef) == Memory::CONFIG_MEMORY_SIZE,
               "Config Memory structure size is wrong");
 
-class Handler {
+class Handler : public BackingMem {
 public:
     Handler();
     ConfigMemDef& GetConfigMem();
 
+    u8* GetPtr() override {
+        return reinterpret_cast<u8*>(&config_mem);
+    }
+
+    const u8* GetPtr() const override {
+        return reinterpret_cast<const u8*>(&config_mem);
+    }
+
+    std::size_t GetSize() const override {
+        return sizeof(config_mem);
+    }
+
 private:
     ConfigMemDef config_mem;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<BackingMem>(*this);
+        ar& boost::serialization::make_binary_object(&config_mem, sizeof(config_mem));
+    }
 };
 
 } // namespace ConfigMem
+
+BOOST_CLASS_EXPORT_KEY(ConfigMem::Handler)
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp
index f31162e35..9f26847b0 100644
--- a/src/core/hle/kernel/event.cpp
+++ b/src/core/hle/kernel/event.cpp
@@ -5,11 +5,14 @@
 #include <algorithm>
 #include <map>
 #include <vector>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/thread.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::Event)
+
 namespace Kernel {
 
 Event::Event(KernelSystem& kernel) : WaitObject(kernel) {}
diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h
index efc4a0c28..02eabd750 100644
--- a/src/core/hle/kernel/event.h
+++ b/src/core/hle/kernel/event.h
@@ -4,6 +4,9 @@
 
 #pragma once
 
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/wait_object.h"
@@ -49,6 +52,18 @@ private:
     std::string name; ///< Name of event (optional)
 
     friend class KernelSystem;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<WaitObject>(*this);
+        ar& reset_type;
+        ar& signaled;
+        ar& name;
+    }
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::Event)
+CONSTRUCT_KERNEL_OBJECT(Kernel::Event)
diff --git a/src/core/hle/kernel/handle_table.h b/src/core/hle/kernel/handle_table.h
index bd5a5df6a..728ab5fa3 100644
--- a/src/core/hle/kernel/handle_table.h
+++ b/src/core/hle/kernel/handle_table.h
@@ -7,6 +7,8 @@
 #include <array>
 #include <cstddef>
 #include <memory>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/result.h"
@@ -116,6 +118,15 @@ private:
     u16 next_free_slot;
 
     KernelSystem& kernel;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& objects;
+        ar& generations;
+        ar& next_generation;
+        ar& next_free_slot;
+    }
 };
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index ab4ecfd05..88bd6b71b 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -16,6 +16,47 @@
 
 namespace Kernel {
 
+class HLERequestContext::ThreadCallback : public Kernel::WakeupCallback {
+
+public:
+    ThreadCallback(std::shared_ptr<HLERequestContext> context_,
+                   std::shared_ptr<HLERequestContext::WakeupCallback> callback_)
+        : context(std::move(context_)), callback(std::move(callback_)) {}
+    void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
+                std::shared_ptr<WaitObject> object) {
+        ASSERT(thread->status == ThreadStatus::WaitHleEvent);
+        if (callback) {
+            callback->WakeUp(thread, *context, reason);
+        }
+
+        auto& process = thread->owner_process;
+        // We must copy the entire command buffer *plus* the entire static buffers area, since
+        // the translation might need to read from it in order to retrieve the StaticBuffer
+        // target addresses.
+        std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2 * IPC::MAX_STATIC_BUFFERS> cmd_buff;
+        Memory::MemorySystem& memory = context->kernel.memory;
+        memory.ReadBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(),
+                         cmd_buff.size() * sizeof(u32));
+        context->WriteToOutgoingCommandBuffer(cmd_buff.data(), *process);
+        // Copy the translated command buffer back into the thread's command buffer area.
+        memory.WriteBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(),
+                          cmd_buff.size() * sizeof(u32));
+    }
+
+private:
+    ThreadCallback() = default;
+    std::shared_ptr<HLERequestContext::WakeupCallback> callback{};
+    std::shared_ptr<HLERequestContext> context{};
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::WakeupCallback>(*this);
+        ar& callback;
+        ar& context;
+    }
+    friend class boost::serialization::access;
+};
+
 SessionRequestHandler::SessionInfo::SessionInfo(std::shared_ptr<ServerSession> session,
                                                 std::unique_ptr<SessionDataBase> data)
     : session(std::move(session)), data(std::move(data)) {}
@@ -33,34 +74,16 @@ void SessionRequestHandler::ClientDisconnected(std::shared_ptr<ServerSession> se
         connected_sessions.end());
 }
 
-std::shared_ptr<Event> HLERequestContext::SleepClientThread(const std::string& reason,
-                                                            std::chrono::nanoseconds timeout,
-                                                            WakeupCallback&& callback) {
+std::shared_ptr<Event> HLERequestContext::SleepClientThread(
+    const std::string& reason, std::chrono::nanoseconds timeout,
+    std::shared_ptr<WakeupCallback> callback) {
     // Put the client thread to sleep until the wait event is signaled or the timeout expires.
-    thread->wakeup_callback = [context = *this,
-                               callback](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
-                                         std::shared_ptr<WaitObject> object) mutable {
-        ASSERT(thread->status == ThreadStatus::WaitHleEvent);
-        callback(thread, context, reason);
-
-        auto& process = thread->owner_process;
-        // We must copy the entire command buffer *plus* the entire static buffers area, since
-        // the translation might need to read from it in order to retrieve the StaticBuffer
-        // target addresses.
-        std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2 * IPC::MAX_STATIC_BUFFERS> cmd_buff;
-        Memory::MemorySystem& memory = context.kernel.memory;
-        memory.ReadBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(),
-                         cmd_buff.size() * sizeof(u32));
-        context.WriteToOutgoingCommandBuffer(cmd_buff.data(), *process);
-        // Copy the translated command buffer back into the thread's command buffer area.
-        memory.WriteBlock(*process, thread->GetCommandBufferAddress(), cmd_buff.data(),
-                          cmd_buff.size() * sizeof(u32));
-    };
+    thread->wakeup_callback = std::make_shared<ThreadCallback>(shared_from_this(), callback);
 
     auto event = kernel.CreateEvent(Kernel::ResetType::OneShot, "HLE Pause Event: " + reason);
     thread->status = ThreadStatus::WaitHleEvent;
     thread->wait_objects = {event};
-    event->AddWaitingThread(SharedFrom(thread));
+    event->AddWaitingThread(thread);
 
     if (timeout.count() > 0)
         thread->WakeAfterDelay(timeout.count());
@@ -68,8 +91,10 @@ std::shared_ptr<Event> HLERequestContext::SleepClientThread(const std::string& r
     return event;
 }
 
+HLERequestContext::HLERequestContext() : kernel(Core::Global<KernelSystem>()) {}
+
 HLERequestContext::HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session,
-                                     Thread* thread)
+                                     std::shared_ptr<Thread> thread)
     : kernel(kernel), session(std::move(session)), thread(thread) {
     cmd_buf[0] = 0;
 }
@@ -98,8 +123,9 @@ void HLERequestContext::AddStaticBuffer(u8 buffer_id, std::vector<u8> data) {
     static_buffers[buffer_id] = std::move(data);
 }
 
-ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
-                                                                Process& src_process) {
+ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(
+    const u32_le* src_cmdbuf, std::shared_ptr<Process> src_process_) {
+    auto& src_process = *src_process_;
     IPC::Header header{src_cmdbuf[0]};
 
     std::size_t untranslated_size = 1u + header.normal_params_size;
@@ -158,7 +184,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
         }
         case IPC::DescriptorType::MappedBuffer: {
             u32 next_id = static_cast<u32>(request_mapped_buffers.size());
-            request_mapped_buffers.emplace_back(kernel.memory, src_process, descriptor,
+            request_mapped_buffers.emplace_back(kernel.memory, src_process_, descriptor,
                                                 src_cmdbuf[i], next_id);
             cmd_buf[i++] = next_id;
             break;
@@ -170,7 +196,7 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
 
     if (should_record) {
         std::vector<u32> translated_cmdbuf{cmd_buf.begin(), cmd_buf.begin() + command_size};
-        kernel.GetIPCRecorder().SetRequestInfo(SharedFrom(thread), std::move(untranslated_cmdbuf),
+        kernel.GetIPCRecorder().SetRequestInfo(thread, std::move(untranslated_cmdbuf),
                                                std::move(translated_cmdbuf));
     }
 
@@ -248,7 +274,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf,
 
     if (should_record) {
         std::vector<u32> translated_cmdbuf{dst_cmdbuf, dst_cmdbuf + command_size};
-        kernel.GetIPCRecorder().SetReplyInfo(SharedFrom(thread), std::move(untranslated_cmdbuf),
+        kernel.GetIPCRecorder().SetReplyInfo(thread, std::move(untranslated_cmdbuf),
                                              std::move(translated_cmdbuf));
     }
 
@@ -262,13 +288,15 @@ MappedBuffer& HLERequestContext::GetMappedBuffer(u32 id_from_cmdbuf) {
 
 void HLERequestContext::ReportUnimplemented() const {
     if (kernel.GetIPCRecorder().IsEnabled()) {
-        kernel.GetIPCRecorder().SetHLEUnimplemented(SharedFrom(thread));
+        kernel.GetIPCRecorder().SetHLEUnimplemented(thread);
     }
 }
 
-MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, const Process& process, u32 descriptor,
-                           VAddr address, u32 id)
-    : memory(&memory), id(id), address(address), process(&process) {
+MappedBuffer::MappedBuffer() : memory(&Core::Global<Core::System>().Memory()) {}
+
+MappedBuffer::MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> process,
+                           u32 descriptor, VAddr address, u32 id)
+    : memory(&memory), id(id), address(address), process(std::move(process)) {
     IPC::MappedBufferDescInfo desc{descriptor};
     size = desc.size;
     perms = desc.perms;
@@ -287,3 +315,5 @@ void MappedBuffer::Write(const void* src_buffer, std::size_t offset, std::size_t
 }
 
 } // namespace Kernel
+
+SERIALIZE_EXPORT_IMPL(Kernel::HLERequestContext::ThreadCallback)
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 26942fe6b..47d98af2c 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -11,7 +11,12 @@
 #include <string>
 #include <vector>
 #include <boost/container/small_vector.hpp>
+#include <boost/serialization/assume_abstract.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unique_ptr.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
+#include "common/serialization/boost_small_vector.hpp"
 #include "common/swap.h"
 #include "core/hle/ipc.h"
 #include "core/hle/kernel/object.h"
@@ -68,6 +73,11 @@ public:
     /// in each service must inherit from this.
     struct SessionDataBase {
         virtual ~SessionDataBase() = default;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int file_version) {}
+        friend class boost::serialization::access;
     };
 
 protected:
@@ -90,15 +100,33 @@ protected:
 
         std::shared_ptr<ServerSession> session;
         std::unique_ptr<SessionDataBase> data;
+
+    private:
+        SessionInfo() = default;
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int file_version) {
+            ar& session;
+            ar& data;
+        }
+        friend class boost::serialization::access;
     };
     /// List of sessions that are connected to this handler. A ServerSession whose server endpoint
     /// is an HLE implementation is kept alive by this list for the duration of the connection.
     std::vector<SessionInfo> connected_sessions;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& connected_sessions;
+    }
+    friend class boost::serialization::access;
 };
 
+// NOTE: The below classes are ephemeral and don't need serialization
+
 class MappedBuffer {
 public:
-    MappedBuffer(Memory::MemorySystem& memory, const Process& process, u32 descriptor,
+    MappedBuffer(Memory::MemorySystem& memory, std::shared_ptr<Process> process, u32 descriptor,
                  VAddr address, u32 id);
 
     // interface for service
@@ -122,9 +150,21 @@ private:
     Memory::MemorySystem* memory;
     u32 id;
     VAddr address;
-    const Process* process;
-    std::size_t size;
+    std::shared_ptr<Process> process;
+    u32 size;
     IPC::MappedBufferPermissions perms;
+
+    MappedBuffer();
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& id;
+        ar& address;
+        ar& process;
+        ar& size;
+        ar& perms;
+    }
+    friend class boost::serialization::access;
 };
 
 /**
@@ -156,9 +196,10 @@ private:
  * id of the memory interface and let kernel convert it back to client vaddr. No real unmapping is
  * needed in this case, though.
  */
-class HLERequestContext {
+class HLERequestContext : public std::enable_shared_from_this<HLERequestContext> {
 public:
-    HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session, Thread* thread);
+    HLERequestContext(KernelSystem& kernel, std::shared_ptr<ServerSession> session,
+                      std::shared_ptr<Thread> thread);
     ~HLERequestContext();
 
     /// Returns a pointer to the IPC command buffer for this request.
@@ -174,8 +215,17 @@ public:
         return session;
     }
 
-    using WakeupCallback = std::function<void(
-        std::shared_ptr<Thread> thread, HLERequestContext& context, ThreadWakeupReason reason)>;
+    class WakeupCallback {
+    public:
+        virtual ~WakeupCallback() = default;
+        virtual void WakeUp(std::shared_ptr<Thread> thread, HLERequestContext& context,
+                            ThreadWakeupReason reason) = 0;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {}
+        friend class boost::serialization::access;
+    };
 
     /**
      * Puts the specified guest thread to sleep until the returned event is signaled or until the
@@ -190,7 +240,7 @@ public:
      */
     std::shared_ptr<Event> SleepClientThread(const std::string& reason,
                                              std::chrono::nanoseconds timeout,
-                                             WakeupCallback&& callback);
+                                             std::shared_ptr<WakeupCallback> callback);
 
     /**
      * Resolves a object id from the request command buffer into a pointer to an object. See the
@@ -230,24 +280,42 @@ public:
     MappedBuffer& GetMappedBuffer(u32 id_from_cmdbuf);
 
     /// Populates this context with data from the requesting process/thread.
-    ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process);
+    ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
+                                                 std::shared_ptr<Process> src_process);
     /// Writes data from this context back to the requesting process/thread.
     ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process) const;
 
     /// Reports an unimplemented function.
     void ReportUnimplemented() const;
 
+    class ThreadCallback;
+    friend class ThreadCallback;
+
 private:
     KernelSystem& kernel;
     std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
     std::shared_ptr<ServerSession> session;
-    Thread* thread;
+    std::shared_ptr<Thread> thread;
     // TODO(yuriks): Check common usage of this and optimize size accordingly
     boost::container::small_vector<std::shared_ptr<Object>, 8> request_handles;
     // The static buffers will be created when the IPC request is translated.
     std::array<std::vector<u8>, IPC::MAX_STATIC_BUFFERS> static_buffers;
     // The mapped buffers will be created when the IPC request is translated
     boost::container::small_vector<MappedBuffer, 8> request_mapped_buffers;
+
+    HLERequestContext();
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& cmd_buf;
+        ar& session;
+        ar& thread;
+        ar& request_handles;
+        ar& static_buffers;
+        ar& request_mapped_buffers;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::HLERequestContext::ThreadCallback)
diff --git a/src/core/hle/kernel/ipc.cpp b/src/core/hle/kernel/ipc.cpp
index 3dacb9831..eb1488840 100644
--- a/src/core/hle/kernel/ipc.cpp
+++ b/src/core/hle/kernel/ipc.cpp
@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include "common/alignment.h"
+#include "common/memory_ref.h"
 #include "core/core.h"
 #include "core/hle/ipc.h"
 #include "core/hle/kernel/handle_table.h"
@@ -71,7 +72,7 @@ ResultCode TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySy
                 if (handle == CurrentThread) {
                     object = src_thread;
                 } else if (handle == CurrentProcess) {
-                    object = SharedFrom(src_process);
+                    object = src_process;
                 } else if (handle != 0) {
                     object = src_process->handle_table.GetGeneric(handle);
                     if (descriptor == IPC::DescriptorType::MoveHandle) {
@@ -193,28 +194,29 @@ ResultCode TranslateCommandBuffer(Kernel::KernelSystem& kernel, Memory::MemorySy
             // TODO(Subv): Perform permission checks.
 
             // Reserve a page of memory before the mapped buffer
-            auto reserve_buffer = std::make_unique<u8[]>(Memory::PAGE_SIZE);
+            std::shared_ptr<BackingMem> reserve_buffer =
+                std::make_shared<BufferMem>(Memory::PAGE_SIZE);
             dst_process->vm_manager.MapBackingMemoryToBase(
-                Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, reserve_buffer.get(),
+                Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, reserve_buffer,
                 Memory::PAGE_SIZE, Kernel::MemoryState::Reserved);
 
-            auto buffer = std::make_unique<u8[]>(num_pages * Memory::PAGE_SIZE);
-            memory.ReadBlock(*src_process, source_address, buffer.get() + page_offset, size);
+            std::shared_ptr<BackingMem> buffer =
+                std::make_shared<BufferMem>(num_pages * Memory::PAGE_SIZE);
+            memory.ReadBlock(*src_process, source_address, buffer->GetPtr() + page_offset, size);
 
             // Map the page(s) into the target process' address space.
             target_address =
                 dst_process->vm_manager
                     .MapBackingMemoryToBase(Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE,
-                                            buffer.get(), num_pages * Memory::PAGE_SIZE,
-                                            Kernel::MemoryState::Shared)
+                                            buffer, buffer->GetSize(), Kernel::MemoryState::Shared)
                     .Unwrap();
 
             cmd_buf[i++] = target_address + page_offset;
 
             // Reserve a page of memory after the mapped buffer
             dst_process->vm_manager.MapBackingMemoryToBase(
-                Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, reserve_buffer.get(),
-                Memory::PAGE_SIZE, Kernel::MemoryState::Reserved);
+                Memory::IPC_MAPPING_VADDR, Memory::IPC_MAPPING_SIZE, reserve_buffer,
+                reserve_buffer->GetSize(), Kernel::MemoryState::Reserved);
 
             mapped_buffer_context.push_back({permissions, size, source_address,
                                              target_address + page_offset, std::move(buffer),
diff --git a/src/core/hle/kernel/ipc.h b/src/core/hle/kernel/ipc.h
index b06079958..2a5fcb4b2 100644
--- a/src/core/hle/kernel/ipc.h
+++ b/src/core/hle/kernel/ipc.h
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <vector>
+#include <boost/serialization/shared_ptr.hpp>
 #include "common/common_types.h"
 #include "core/hle/ipc.h"
 #include "core/hle/kernel/thread.h"
@@ -24,8 +25,20 @@ struct MappedBufferContext {
     VAddr source_address;
     VAddr target_address;
 
-    std::unique_ptr<u8[]> buffer;
-    std::unique_ptr<u8[]> reserve_buffer;
+    std::shared_ptr<BackingMem> buffer;
+    std::shared_ptr<BackingMem> reserve_buffer;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& permissions;
+        ar& size;
+        ar& source_address;
+        ar& target_address;
+        ar& buffer;
+        ar& reserve_buffer;
+    }
+    friend class boost::serialization::access;
 };
 
 /// Performs IPC command buffer translation from one process to another.
diff --git a/src/core/hle/kernel/ipc_debugger/recorder.cpp b/src/core/hle/kernel/ipc_debugger/recorder.cpp
index 968815c5b..f1e4a09f1 100644
--- a/src/core/hle/kernel/ipc_debugger/recorder.cpp
+++ b/src/core/hle/kernel/ipc_debugger/recorder.cpp
@@ -52,7 +52,7 @@ void Recorder::RegisterRequest(const std::shared_ptr<Kernel::ClientSession>& cli
 
     RequestRecord record = {/* id */ ++record_count,
                             /* status */ RequestStatus::Sent,
-                            /* client_process */ GetObjectInfo(client_thread->owner_process),
+                            /* client_process */ GetObjectInfo(client_thread->owner_process.get()),
                             /* client_thread */ GetObjectInfo(client_thread.get()),
                             /* client_session */ GetObjectInfo(client_session.get()),
                             /* client_port */ GetObjectInfo(client_session->parent->port.get()),
@@ -82,7 +82,7 @@ void Recorder::SetRequestInfo(const std::shared_ptr<Kernel::Thread>& client_thre
     record.translated_request_cmdbuf = std::move(translated_cmdbuf);
 
     if (server_thread) {
-        record.server_process = GetObjectInfo(server_thread->owner_process);
+        record.server_process = GetObjectInfo(server_thread->owner_process.get());
         record.server_thread = GetObjectInfo(server_thread.get());
     } else {
         record.is_hle = true;
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 8b2d89ead..d36c8a4c7 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -2,6 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unordered_map.hpp>
+#include <boost/serialization/vector.hpp>
+#include "common/archives.h"
+#include "common/serialization/atomic.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/config_mem.h"
 #include "core/hle/kernel/handle_table.h"
@@ -22,6 +27,8 @@ KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
                            u32 num_cores, u8 n3ds_mode)
     : memory(memory), timing(timing),
       prepare_reschedule_callback(std::move(prepare_reschedule_callback)) {
+    std::generate(memory_regions.begin(), memory_regions.end(),
+                  [] { return std::make_shared<MemoryRegionInfo>(); });
     MemoryInit(system_mode, n3ds_mode);
 
     resource_limits = std::make_unique<ResourceLimitList>(*this);
@@ -58,24 +65,23 @@ std::shared_ptr<Process> KernelSystem::GetCurrentProcess() const {
 
 void KernelSystem::SetCurrentProcess(std::shared_ptr<Process> process) {
     current_process = process;
-    SetCurrentMemoryPageTable(&process->vm_manager.page_table);
+    SetCurrentMemoryPageTable(process->vm_manager.page_table);
 }
 
 void KernelSystem::SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id) {
     if (current_cpu->GetID() == core_id) {
         current_process = process;
-        SetCurrentMemoryPageTable(&process->vm_manager.page_table);
+        SetCurrentMemoryPageTable(process->vm_manager.page_table);
     } else {
         stored_processes[core_id] = process;
-        thread_managers[core_id]->cpu->PageTableChanged(&process->vm_manager.page_table);
+        thread_managers[core_id]->cpu->SetPageTable(process->vm_manager.page_table);
     }
 }
 
-void KernelSystem::SetCurrentMemoryPageTable(Memory::PageTable* page_table) {
+void KernelSystem::SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table) {
     memory.SetCurrentPageTable(page_table);
     if (current_cpu != nullptr) {
-        // Notify the CPU the page table in memory has changed
-        current_cpu->PageTableChanged(page_table);
+        current_cpu->SetPageTable(page_table);
     }
 }
 
@@ -150,4 +156,29 @@ void KernelSystem::ResetThreadIDs() {
     next_thread_id = 0;
 }
 
+template <class Archive>
+void KernelSystem::serialize(Archive& ar, const unsigned int file_version) {
+    ar& memory_regions;
+    ar& named_ports;
+    // current_cpu set externally
+    // NB: subsystem references and prepare_reschedule_callback are constant
+    ar&* resource_limits.get();
+    ar& next_object_id;
+    ar&* timer_manager.get();
+    ar& next_process_id;
+    ar& process_list;
+    ar& current_process;
+    // NB: core count checked in 'core'
+    for (auto& thread_manager : thread_managers) {
+        ar&* thread_manager.get();
+    }
+    ar& config_mem_handler;
+    ar& shared_page_handler;
+    ar& stored_processes;
+    ar& next_thread_id;
+    // Deliberately don't include debugger info to allow debugging through loads
+}
+
+SERIALIZE_IMPL(KernelSystem)
+
 } // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index c275d7ce4..f0a8368d9 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -132,7 +132,8 @@ public:
      */
     ResultVal<std::shared_ptr<Thread>> CreateThread(std::string name, VAddr entry_point,
                                                     u32 priority, u32 arg, s32 processor_id,
-                                                    VAddr stack_top, Process& owner_process);
+                                                    VAddr stack_top,
+                                                    std::shared_ptr<Process> owner_process);
 
     /**
      * Creates a semaphore.
@@ -213,7 +214,7 @@ public:
     void SetCurrentProcess(std::shared_ptr<Process> process);
     void SetCurrentProcessForCPU(std::shared_ptr<Process> process, u32 core_id);
 
-    void SetCurrentMemoryPageTable(Memory::PageTable* page_table);
+    void SetCurrentMemoryPageTable(std::shared_ptr<Memory::PageTable> page_table);
 
     void SetCPUs(std::vector<std::shared_ptr<ARM_Interface>> cpu);
 
@@ -236,11 +237,11 @@ public:
     IPCDebugger::Recorder& GetIPCRecorder();
     const IPCDebugger::Recorder& GetIPCRecorder() const;
 
-    MemoryRegionInfo* GetMemoryRegion(MemoryRegion region);
+    std::shared_ptr<MemoryRegionInfo> GetMemoryRegion(MemoryRegion region);
 
     void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);
 
-    std::array<MemoryRegionInfo, 3> memory_regions;
+    std::array<std::shared_ptr<MemoryRegionInfo>, 3> memory_regions{};
 
     /// Adds a port to the named port table
     void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port);
@@ -291,12 +292,16 @@ private:
 
     std::vector<std::unique_ptr<ThreadManager>> thread_managers;
 
-    std::unique_ptr<ConfigMem::Handler> config_mem_handler;
-    std::unique_ptr<SharedPage::Handler> shared_page_handler;
+    std::shared_ptr<ConfigMem::Handler> config_mem_handler;
+    std::shared_ptr<SharedPage::Handler> shared_page_handler;
 
     std::unique_ptr<IPCDebugger::Recorder> ipc_recorder;
 
     u32 next_thread_id;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 7b6425bbd..a4e77e0b2 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -71,32 +71,32 @@ void KernelSystem::MemoryInit(u32 mem_type, u8 n3ds_mode) {
     // the sizes specified in the memory_region_sizes table.
     VAddr base = 0;
     for (int i = 0; i < 3; ++i) {
-        memory_regions[i].Reset(base, memory_region_sizes[mem_type][i]);
+        memory_regions[i]->Reset(base, memory_region_sizes[mem_type][i]);
 
-        base += memory_regions[i].size;
+        base += memory_regions[i]->size;
     }
 
     // We must've allocated the entire FCRAM by the end
     ASSERT(base == (is_new_3ds ? Memory::FCRAM_N3DS_SIZE : Memory::FCRAM_SIZE));
 
-    config_mem_handler = std::make_unique<ConfigMem::Handler>();
+    config_mem_handler = std::make_shared<ConfigMem::Handler>();
     auto& config_mem = config_mem_handler->GetConfigMem();
     config_mem.app_mem_type = reported_mem_type;
     config_mem.app_mem_alloc = memory_region_sizes[reported_mem_type][0];
-    config_mem.sys_mem_alloc = memory_regions[1].size;
-    config_mem.base_mem_alloc = memory_regions[2].size;
+    config_mem.sys_mem_alloc = memory_regions[1]->size;
+    config_mem.base_mem_alloc = memory_regions[2]->size;
 
-    shared_page_handler = std::make_unique<SharedPage::Handler>(timing);
+    shared_page_handler = std::make_shared<SharedPage::Handler>(timing);
 }
 
-MemoryRegionInfo* KernelSystem::GetMemoryRegion(MemoryRegion region) {
+std::shared_ptr<MemoryRegionInfo> KernelSystem::GetMemoryRegion(MemoryRegion region) {
     switch (region) {
     case MemoryRegion::APPLICATION:
-        return &memory_regions[0];
+        return memory_regions[0];
     case MemoryRegion::SYSTEM:
-        return &memory_regions[1];
+        return memory_regions[1];
     case MemoryRegion::BASE:
-        return &memory_regions[2];
+        return memory_regions[2];
     default:
         UNREACHABLE();
     }
@@ -147,7 +147,7 @@ void KernelSystem::HandleSpecialMapping(VMManager& address_space, const AddressM
         return;
     }
 
-    u8* target_pointer = memory.GetPhysicalPointer(area->paddr_base + offset_into_region);
+    auto target_pointer = memory.GetPhysicalRef(area->paddr_base + offset_into_region);
 
     // TODO(yuriks): This flag seems to have some other effect, but it's unknown what
     MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO;
@@ -160,20 +160,16 @@ void KernelSystem::HandleSpecialMapping(VMManager& address_space, const AddressM
 }
 
 void KernelSystem::MapSharedPages(VMManager& address_space) {
-    auto cfg_mem_vma =
-        address_space
-            .MapBackingMemory(Memory::CONFIG_MEMORY_VADDR,
-                              reinterpret_cast<u8*>(&config_mem_handler->GetConfigMem()),
-                              Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared)
-            .Unwrap();
+    auto cfg_mem_vma = address_space
+                           .MapBackingMemory(Memory::CONFIG_MEMORY_VADDR, {config_mem_handler},
+                                             Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared)
+                           .Unwrap();
     address_space.Reprotect(cfg_mem_vma, VMAPermission::Read);
 
-    auto shared_page_vma =
-        address_space
-            .MapBackingMemory(Memory::SHARED_PAGE_VADDR,
-                              reinterpret_cast<u8*>(&shared_page_handler->GetSharedPage()),
-                              Memory::SHARED_PAGE_SIZE, MemoryState::Shared)
-            .Unwrap();
+    auto shared_page_vma = address_space
+                               .MapBackingMemory(Memory::SHARED_PAGE_VADDR, {shared_page_handler},
+                                                 Memory::SHARED_PAGE_SIZE, MemoryState::Shared)
+                               .Unwrap();
     address_space.Reprotect(shared_page_vma, VMAPermission::Read);
 }
 
diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h
index bb4e174f7..739369a9c 100644
--- a/src/core/hle/kernel/memory.h
+++ b/src/core/hle/kernel/memory.h
@@ -6,7 +6,9 @@
 
 #include <optional>
 #include <boost/icl/interval_set.hpp>
+#include <boost/serialization/set.hpp>
 #include "common/common_types.h"
+#include "common/serialization/boost_interval_set.hpp"
 
 namespace Kernel {
 
@@ -60,6 +62,16 @@ struct MemoryRegionInfo {
      * @param size the size of the region to free.
      */
     void Free(u32 offset, u32 size);
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& base;
+        ar& size;
+        ar& used;
+        ar& free_blocks;
+    }
 };
 
 } // namespace Kernel
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 30dd1eb55..77a5fe903 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -4,14 +4,18 @@
 
 #include <map>
 #include <vector>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "core/core.h"
+#include "core/global.h"
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/mutex.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/thread.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::Mutex)
+
 namespace Kernel {
 
 void ReleaseThreadMutexes(Thread* thread) {
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
index 1f6358909..db98aa8c9 100644
--- a/src/core/hle/kernel/mutex.h
+++ b/src/core/hle/kernel/mutex.h
@@ -6,6 +6,10 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/wait_object.h"
@@ -58,6 +62,16 @@ public:
 
 private:
     KernelSystem& kernel;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<WaitObject>(*this);
+        ar& lock_count;
+        ar& priority;
+        ar& name;
+        ar& holding_thread;
+    }
 };
 
 /**
@@ -67,3 +81,6 @@ private:
 void ReleaseThreadMutexes(Thread* thread);
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::Mutex)
+CONSTRUCT_KERNEL_OBJECT(Kernel::Mutex)
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index 9547a83ed..b272366d7 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -7,7 +7,12 @@
 #include <atomic>
 #include <memory>
 #include <string>
+#include <boost/serialization/access.hpp>
+#include <boost/serialization/assume_abstract.hpp>
+#include <boost/serialization/export.hpp>
 #include "common/common_types.h"
+#include "common/serialization/atomic.h"
+#include "core/global.h"
 #include "core/hle/kernel/kernel.h"
 
 namespace Kernel {
@@ -64,6 +69,12 @@ public:
 
 private:
     std::atomic<u32> object_id;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& object_id;
+    }
 };
 
 template <typename T>
@@ -87,3 +98,13 @@ inline std::shared_ptr<T> DynamicObjectCast(std::shared_ptr<Object> object) {
 }
 
 } // namespace Kernel
+
+BOOST_SERIALIZATION_ASSUME_ABSTRACT(Kernel::Object)
+
+#define CONSTRUCT_KERNEL_OBJECT(T)                                                                 \
+    namespace boost::serialization {                                                               \
+    template <class Archive>                                                                       \
+    void load_construct_data(Archive& ar, T* t, const unsigned int file_version) {                 \
+        ::new (t) T(Core::Global<Kernel::KernelSystem>());                                         \
+    }                                                                                              \
+    }
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index c6aa2b895..9b03f30f9 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -4,9 +4,14 @@
 
 #include <algorithm>
 #include <memory>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/bitset.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/common_funcs.h"
 #include "common/logging/log.h"
+#include "common/serialization/boost_vector.hpp"
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/process.h"
@@ -15,8 +20,34 @@
 #include "core/hle/kernel/vm_manager.h"
 #include "core/memory.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::Process)
+SERIALIZE_EXPORT_IMPL(Kernel::CodeSet)
+
 namespace Kernel {
 
+template <class Archive>
+void Process::serialize(Archive& ar, const unsigned int file_version) {
+    ar& boost::serialization::base_object<Object>(*this);
+    ar& handle_table;
+    ar& codeset; // TODO: Replace with apploader reference
+    ar& resource_limit;
+    ar& svc_access_mask;
+    ar& handle_table_size;
+    ar&(boost::container::vector<AddressMapping, boost::container::dtl::static_storage_allocator<
+                                                     AddressMapping, 8, 0, true>>&)address_mappings;
+    ar& flags.raw;
+    ar& kernel_version;
+    ar& ideal_processor;
+    ar& status;
+    ar& process_id;
+    ar& vm_manager;
+    ar& memory_used;
+    ar& memory_region;
+    ar& tls_slots;
+}
+
+SERIALIZE_IMPL(Process)
+
 std::shared_ptr<CodeSet> KernelSystem::CreateCodeSet(std::string name, u64 program_id) {
     auto codeset{std::make_shared<CodeSet>(*this)};
 
@@ -191,7 +222,7 @@ ResultVal<VAddr> Process::HeapAllocate(VAddr target, u32 size, VMAPermission per
         std::fill(kernel.memory.GetFCRAMPointer(interval.lower()),
                   kernel.memory.GetFCRAMPointer(interval.upper()), 0);
         auto vma = vm_manager.MapBackingMemory(interval_target,
-                                               kernel.memory.GetFCRAMPointer(interval.lower()),
+                                               kernel.memory.GetFCRAMRef(interval.lower()),
                                                interval_size, memory_state);
         ASSERT(vma.Succeeded());
         vm_manager.Reprotect(vma.Unwrap(), perms);
@@ -219,7 +250,7 @@ ResultCode Process::HeapFree(VAddr target, u32 size) {
     // Free heaps block by block
     CASCADE_RESULT(auto backing_blocks, vm_manager.GetBackingBlocksForRange(target, size));
     for (const auto [backing_memory, block_size] : backing_blocks) {
-        memory_region->Free(kernel.memory.GetFCRAMOffset(backing_memory), block_size);
+        memory_region->Free(kernel.memory.GetFCRAMOffset(backing_memory.GetPtr()), block_size);
     }
 
     ResultCode result = vm_manager.UnmapRange(target, size);
@@ -263,9 +294,9 @@ ResultVal<VAddr> Process::LinearAllocate(VAddr target, u32 size, VMAPermission p
         }
     }
 
-    u8* backing_memory = kernel.memory.GetFCRAMPointer(physical_offset);
+    auto backing_memory = kernel.memory.GetFCRAMRef(physical_offset);
 
-    std::fill(backing_memory, backing_memory + size, 0);
+    std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + size, 0);
     auto vma = vm_manager.MapBackingMemory(target, backing_memory, size, MemoryState::Continuous);
     ASSERT(vma.Succeeded());
     vm_manager.Reprotect(vma.Unwrap(), perms);
@@ -403,8 +434,7 @@ ResultCode Process::Unmap(VAddr target, VAddr source, u32 size, VMAPermission pe
 
 Kernel::Process::Process(KernelSystem& kernel)
     : Object(kernel), handle_table(kernel), vm_manager(kernel.memory), kernel(kernel) {
-
-    kernel.memory.RegisterPageTable(&vm_manager.page_table);
+    kernel.memory.RegisterPageTable(vm_manager.page_table);
 }
 Kernel::Process::~Process() {
     // Release all objects this process owns first so that their potential destructor can do clean
@@ -413,7 +443,7 @@ Kernel::Process::~Process() {
     // memory etc.) even if they are still referenced by other processes.
     handle_table.Clear();
 
-    kernel.memory.UnregisterPageTable(&vm_manager.page_table);
+    kernel.memory.UnregisterPageTable(vm_manager.page_table);
 }
 
 std::shared_ptr<Process> KernelSystem::GetProcessById(u32 process_id) const {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index dc2c878b8..4eca568d9 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -11,6 +11,10 @@
 #include <string>
 #include <vector>
 #include <boost/container/static_vector.hpp>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "core/hle/kernel/handle_table.h"
@@ -25,6 +29,16 @@ struct AddressMapping {
     u32 size;
     bool read_only;
     bool unk_flag;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& address;
+        ar& size;
+        ar& read_only;
+        ar& unk_flag;
+    }
 };
 
 union ProcessFlags {
@@ -59,6 +73,15 @@ public:
         std::size_t offset = 0;
         VAddr addr = 0;
         u32 size = 0;
+
+    private:
+        friend class boost::serialization::access;
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int file_version) {
+            ar& offset;
+            ar& addr;
+            ar& size;
+        }
     };
 
     std::string GetTypeName() const override {
@@ -106,6 +129,18 @@ public:
     std::string name;
     /// Title ID corresponding to the process
     u64 program_id;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<Object>(*this);
+        ar& memory;
+        ar& segments;
+        ar& entrypoint;
+        ar& name;
+        ar& program_id;
+    }
 };
 
 class Process final : public Object {
@@ -167,7 +202,7 @@ public:
 
     u32 memory_used = 0;
 
-    MemoryRegionInfo* memory_region = nullptr;
+    std::shared_ptr<MemoryRegionInfo> memory_region = nullptr;
 
     /// The Thread Local Storage area is allocated as processes create threads,
     /// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
@@ -195,5 +230,14 @@ public:
 
 private:
     KernelSystem& kernel;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::CodeSet)
+BOOST_CLASS_EXPORT_KEY(Kernel::Process)
+CONSTRUCT_KERNEL_OBJECT(Kernel::CodeSet)
+CONSTRUCT_KERNEL_OBJECT(Kernel::Process)
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
index 8691c142e..9affa685a 100644
--- a/src/core/hle/kernel/resource_limit.cpp
+++ b/src/core/hle/kernel/resource_limit.cpp
@@ -3,10 +3,13 @@
 // Refer to the license.txt file included.
 
 #include <cstring>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "core/hle/kernel/resource_limit.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::ResourceLimit)
+
 namespace Kernel {
 
 ResourceLimit::ResourceLimit(KernelSystem& kernel) : Object(kernel) {}
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h
index 99ae8f2cf..cb1c8c78e 100644
--- a/src/core/hle/kernel/resource_limit.h
+++ b/src/core/hle/kernel/resource_limit.h
@@ -6,6 +6,10 @@
 
 #include <array>
 #include <memory>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
 
@@ -110,6 +114,35 @@ public:
 
     /// Current CPU time that the processes in this category are utilizing
     s32 current_cpu_time = 0;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<Object>(*this);
+        // NB most of these aren't used at all currently, but we're adding them here for forwards
+        // compatibility
+        ar& name;
+        ar& max_priority;
+        ar& max_commit;
+        ar& max_threads;
+        ar& max_events;
+        ar& max_mutexes;
+        ar& max_semaphores;
+        ar& max_timers;
+        ar& max_shared_mems;
+        ar& max_address_arbiters;
+        ar& max_cpu_time;
+        ar& current_commit;
+        ar& current_threads;
+        ar& current_events;
+        ar& current_mutexes;
+        ar& current_semaphores;
+        ar& current_timers;
+        ar& current_shared_mems;
+        ar& current_address_arbiters;
+        ar& current_cpu_time;
+    }
 };
 
 class ResourceLimitList {
@@ -126,6 +159,15 @@ public:
 
 private:
     std::array<std::shared_ptr<ResourceLimit>, 4> resource_limits;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& resource_limits;
+    }
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::ResourceLimit)
+CONSTRUCT_KERNEL_OBJECT(Kernel::ResourceLimit)
diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp
index bbc8a385f..7aefc8605 100644
--- a/src/core/hle/kernel/semaphore.cpp
+++ b/src/core/hle/kernel/semaphore.cpp
@@ -2,12 +2,15 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "common/assert.h"
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/kernel/semaphore.h"
 #include "core/hle/kernel/thread.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::Semaphore)
+
 namespace Kernel {
 
 Semaphore::Semaphore(KernelSystem& kernel) : WaitObject(kernel) {}
diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h
index 47b3eabf1..b9863a838 100644
--- a/src/core/hle/kernel/semaphore.h
+++ b/src/core/hle/kernel/semaphore.h
@@ -5,6 +5,9 @@
 #pragma once
 
 #include <string>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/string.hpp>
 #include <queue>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
@@ -43,6 +46,19 @@ public:
      * @return The number of free slots the semaphore had before this call
      */
     ResultVal<s32> Release(s32 release_count);
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<WaitObject>(*this);
+        ar& max_count;
+        ar& available_count;
+        ar& name;
+    }
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::Semaphore)
+CONSTRUCT_KERNEL_OBJECT(Kernel::Semaphore)
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index a69b42778..006403925 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -3,14 +3,22 @@
 // Refer to the license.txt file included.
 
 #include <tuple>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/hle_ipc.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/server_port.h"
 #include "core/hle/kernel/server_session.h"
 #include "core/hle/kernel/thread.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::ServerPort)
+
 namespace Kernel {
 
 ServerPort::ServerPort(KernelSystem& kernel) : WaitObject(kernel) {}
@@ -48,4 +56,13 @@ KernelSystem::PortPair KernelSystem::CreatePortPair(u32 max_sessions, std::strin
     return std::make_pair(std::move(server_port), std::move(client_port));
 }
 
+template <class Archive>
+void ServerPort::serialize(Archive& ar, const unsigned int file_version) {
+    ar& boost::serialization::base_object<WaitObject>(*this);
+    ar& name;
+    ar& pending_sessions;
+    ar& hle_handler;
+}
+SERIALIZE_IMPL(ServerPort)
+
 } // namespace Kernel
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index 9b0f13480..00eb10100 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -7,8 +7,10 @@
 #include <memory>
 #include <string>
 #include <tuple>
+#include <boost/serialization/export.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
+#include "core/hle/kernel/server_session.h"
 #include "core/hle/kernel/wait_object.h"
 #include "core/hle/result.h"
 
@@ -60,6 +62,14 @@ public:
 
     bool ShouldWait(const Thread* thread) const override;
     void Acquire(Thread* thread) override;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::ServerPort)
+CONSTRUCT_KERNEL_OBJECT(Kernel::ServerPort)
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 5855d83a5..61ade5b70 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -3,7 +3,10 @@
 // Refer to the license.txt file included.
 
 #include <tuple>
-
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/vector.hpp>
+#include "common/archives.h"
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/client_session.h"
 #include "core/hle/kernel/hle_ipc.h"
@@ -11,8 +14,22 @@
 #include "core/hle/kernel/session.h"
 #include "core/hle/kernel/thread.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::ServerSession)
+
 namespace Kernel {
 
+template <class Archive>
+void ServerSession::serialize(Archive& ar, const unsigned int file_version) {
+    ar& boost::serialization::base_object<WaitObject>(*this);
+    ar& name;
+    ar& parent;
+    ar& hle_handler;
+    ar& pending_requesting_threads;
+    ar& currently_handling;
+    ar& mapped_buffer_context;
+}
+SERIALIZE_IMPL(ServerSession)
+
 ServerSession::ServerSession(KernelSystem& kernel) : WaitObject(kernel), kernel(kernel) {}
 ServerSession::~ServerSession() {
     // This destructor will be called automatically when the last ServerSession handle is closed by
@@ -68,14 +85,15 @@ ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread) {
     // If this ServerSession has an associated HLE handler, forward the request to it.
     if (hle_handler != nullptr) {
         std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2 * IPC::MAX_STATIC_BUFFERS> cmd_buf;
-        Kernel::Process* current_process = thread->owner_process;
+        auto current_process = thread->owner_process;
         kernel.memory.ReadBlock(*current_process, thread->GetCommandBufferAddress(), cmd_buf.data(),
                                 cmd_buf.size() * sizeof(u32));
 
-        Kernel::HLERequestContext context(kernel, SharedFrom(this), thread.get());
-        context.PopulateFromIncomingCommandBuffer(cmd_buf.data(), *current_process);
+        auto context =
+            std::make_shared<Kernel::HLERequestContext>(kernel, SharedFrom(this), thread);
+        context->PopulateFromIncomingCommandBuffer(cmd_buf.data(), current_process);
 
-        hle_handler->HandleSyncRequest(context);
+        hle_handler->HandleSyncRequest(*context);
 
         ASSERT(thread->status == Kernel::ThreadStatus::Running ||
                thread->status == Kernel::ThreadStatus::WaitHleEvent);
@@ -83,7 +101,7 @@ ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread) {
         // put the thread to sleep then the writing of the command buffer will be deferred to the
         // wakeup callback.
         if (thread->status == Kernel::ThreadStatus::Running) {
-            context.WriteToOutgoingCommandBuffer(cmd_buf.data(), *current_process);
+            context->WriteToOutgoingCommandBuffer(cmd_buf.data(), *current_process);
             kernel.memory.WriteBlock(*current_process, thread->GetCommandBufferAddress(),
                                      cmd_buf.data(), cmd_buf.size() * sizeof(u32));
         }
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 940f38f9b..b91accce5 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -6,10 +6,12 @@
 
 #include <memory>
 #include <string>
+#include <boost/serialization/export.hpp>
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "core/hle/kernel/ipc.h"
 #include "core/hle/kernel/object.h"
+#include "core/hle/kernel/session.h"
 #include "core/hle/kernel/wait_object.h"
 #include "core/hle/result.h"
 #include "core/memory.h"
@@ -103,6 +105,13 @@ private:
 
     friend class KernelSystem;
     KernelSystem& kernel;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::ServerSession)
+CONSTRUCT_KERNEL_OBJECT(Kernel::ServerSession)
diff --git a/src/core/hle/kernel/session.cpp b/src/core/hle/kernel/session.cpp
index 642914744..8bb846c52 100644
--- a/src/core/hle/kernel/session.cpp
+++ b/src/core/hle/kernel/session.cpp
@@ -2,11 +2,22 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/shared_ptr.hpp>
+#include "common/archives.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/server_session.h"
 #include "core/hle/kernel/session.h"
-#include "core/hle/kernel/thread.h"
+
+SERIALIZE_IMPL(Kernel::Session)
 
 namespace Kernel {
 
-Session::Session() {}
-Session::~Session() {}
+template <class Archive>
+void Session::serialize(Archive& ar, const unsigned int file_version) {
+    ar& client;
+    ar& server;
+    ar& port;
+}
+
 } // namespace Kernel
diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h
index 17bb4d6c6..eca1a9252 100644
--- a/src/core/hle/kernel/session.h
+++ b/src/core/hle/kernel/session.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <memory>
+#include <boost/serialization/access.hpp>
 #include "core/hle/kernel/object.h"
 
 namespace Kernel {
@@ -24,5 +25,10 @@ public:
     ClientSession* client = nullptr;  ///< The client endpoint of the session.
     ServerSession* server = nullptr;  ///< The server endpoint of the session.
     std::shared_ptr<ClientPort> port; ///< The port that this session is associated with (optional).
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 } // namespace Kernel
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 4cd305508..e8f792ee8 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -3,12 +3,15 @@
 // Refer to the license.txt file included.
 
 #include <cstring>
+#include "common/archives.h"
 #include "common/logging/log.h"
 #include "core/hle/kernel/errors.h"
 #include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/shared_memory.h"
 #include "core/memory.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::SharedMemory)
+
 namespace Kernel {
 
 SharedMemory::SharedMemory(KernelSystem& kernel) : Object(kernel), kernel(kernel) {}
@@ -38,13 +41,13 @@ ResultVal<std::shared_ptr<SharedMemory>> KernelSystem::CreateSharedMemory(
     if (address == 0) {
         // We need to allocate a block from the Linear Heap ourselves.
         // We'll manually allocate some memory from the linear heap in the specified region.
-        MemoryRegionInfo* memory_region = GetMemoryRegion(region);
+        auto memory_region = GetMemoryRegion(region);
         auto offset = memory_region->LinearAllocate(size);
 
         ASSERT_MSG(offset, "Not enough space in region to allocate shared memory!");
 
         std::fill(memory.GetFCRAMPointer(*offset), memory.GetFCRAMPointer(*offset + size), 0);
-        shared_memory->backing_blocks = {{memory.GetFCRAMPointer(*offset), size}};
+        shared_memory->backing_blocks = {{memory.GetFCRAMRef(*offset), size}};
         shared_memory->holding_memory += MemoryRegionInfo::Interval(*offset, *offset + size);
         shared_memory->linear_heap_phys_offset = *offset;
 
@@ -75,7 +78,7 @@ std::shared_ptr<SharedMemory> KernelSystem::CreateSharedMemoryForApplet(
     auto shared_memory{std::make_shared<SharedMemory>(*this)};
 
     // Allocate memory in heap
-    MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::SYSTEM);
+    auto memory_region = GetMemoryRegion(MemoryRegion::SYSTEM);
     auto backing_blocks = memory_region->HeapAllocate(size);
     ASSERT_MSG(!backing_blocks.empty(), "Not enough space in region to allocate shared memory!");
     shared_memory->holding_memory = backing_blocks;
@@ -86,7 +89,7 @@ std::shared_ptr<SharedMemory> KernelSystem::CreateSharedMemoryForApplet(
     shared_memory->other_permissions = other_permissions;
     for (const auto& interval : backing_blocks) {
         shared_memory->backing_blocks.push_back(
-            {memory.GetFCRAMPointer(interval.lower()), interval.upper() - interval.lower()});
+            {memory.GetFCRAMRef(interval.lower()), interval.upper() - interval.lower()});
         std::fill(memory.GetFCRAMPointer(interval.lower()),
                   memory.GetFCRAMPointer(interval.upper()), 0);
     }
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h
index 0d781cfcc..0e500a1dd 100644
--- a/src/core/hle/kernel/shared_memory.h
+++ b/src/core/hle/kernel/shared_memory.h
@@ -6,7 +6,11 @@
 
 #include <string>
 #include <utility>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/string.hpp>
 #include "common/common_types.h"
+#include "common/memory_ref.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/result.h"
@@ -86,7 +90,7 @@ private:
     /// during creation.
     PAddr linear_heap_phys_offset = 0;
     /// Backing memory for this shared memory block.
-    std::vector<std::pair<u8*, u32>> backing_blocks;
+    std::vector<std::pair<MemoryRef, u32>> backing_blocks;
     /// Size of the memory block. Page-aligned.
     u32 size = 0;
     /// Permission restrictions applied to the process which created the block.
@@ -104,6 +108,24 @@ private:
 
     friend class KernelSystem;
     KernelSystem& kernel;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<Object>(*this);
+        ar& linear_heap_phys_offset;
+        ar& backing_blocks;
+        ar& size;
+        ar& permissions;
+        ar& other_permissions;
+        ar& owner_process;
+        ar& base_address;
+        ar& name;
+        ar& holding_memory;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::SharedMemory)
+CONSTRUCT_KERNEL_OBJECT(Kernel::SharedMemory)
diff --git a/src/core/hle/kernel/shared_page.cpp b/src/core/hle/kernel/shared_page.cpp
index 30de0ca1f..896055142 100644
--- a/src/core/hle/kernel/shared_page.cpp
+++ b/src/core/hle/kernel/shared_page.cpp
@@ -4,6 +4,7 @@
 
 #include <chrono>
 #include <cstring>
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/hle/kernel/shared_page.h"
@@ -13,6 +14,19 @@
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 
+SERIALIZE_EXPORT_IMPL(SharedPage::Handler)
+
+namespace boost::serialization {
+
+template <class Archive>
+void load_construct_data(Archive& ar, SharedPage::Handler* t, const unsigned int) {
+    ::new (t) SharedPage::Handler(Core::System::GetInstance().CoreTiming());
+}
+template void load_construct_data<iarchive>(iarchive& ar, SharedPage::Handler* t,
+                                            const unsigned int);
+
+} // namespace boost::serialization
+
 namespace SharedPage {
 
 static std::chrono::seconds GetInitTime() {
diff --git a/src/core/hle/kernel/shared_page.h b/src/core/hle/kernel/shared_page.h
index 5092b4869..9cd579d52 100644
--- a/src/core/hle/kernel/shared_page.h
+++ b/src/core/hle/kernel/shared_page.h
@@ -13,9 +13,13 @@
 #include <chrono>
 #include <ctime>
 #include <memory>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/binary_object.hpp>
+#include <boost/serialization/export.hpp>
 #include "common/bit_field.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
+#include "common/memory_ref.h"
 #include "common/swap.h"
 #include "core/memory.h"
 
@@ -82,7 +86,7 @@ struct SharedPageDef {
 static_assert(sizeof(SharedPageDef) == Memory::SHARED_PAGE_SIZE,
               "Shared page structure size is wrong");
 
-class Handler {
+class Handler : public BackingMem {
 public:
     Handler(Core::Timing& timing);
 
@@ -96,6 +100,18 @@ public:
 
     SharedPageDef& GetSharedPage();
 
+    u8* GetPtr() override {
+        return reinterpret_cast<u8*>(&shared_page);
+    }
+
+    const u8* GetPtr() const override {
+        return reinterpret_cast<const u8*>(&shared_page);
+    }
+
+    std::size_t GetSize() const override {
+        return sizeof(shared_page);
+    }
+
 private:
     u64 GetSystemTime() const;
     void UpdateTimeCallback(u64 userdata, int cycles_late);
@@ -104,6 +120,22 @@ private:
     std::chrono::seconds init_time;
 
     SharedPageDef shared_page;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<BackingMem>(*this);
+        ar& boost::serialization::make_binary_object(&shared_page, sizeof(shared_page));
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace SharedPage
+
+namespace boost::serialization {
+
+template <class Archive>
+void load_construct_data(Archive& ar, SharedPage::Handler* t, const unsigned int);
+
+} // namespace boost::serialization
+
+BOOST_CLASS_EXPORT_KEY(SharedPage::Handler)
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index d3a7b0626..25eb9bb4e 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -282,7 +282,7 @@ void SVC::ExitProcess() {
     // Stop all the process threads that are currently waiting for objects.
     auto& thread_list = kernel.GetCurrentThreadManager().GetThreadList();
     for (auto& thread : thread_list) {
-        if (thread->owner_process != current_process.get())
+        if (thread->owner_process != current_process)
             continue;
 
         if (thread.get() == kernel.GetCurrentThreadManager().GetCurrentThread())
@@ -403,6 +403,76 @@ ResultCode SVC::CloseHandle(Handle handle) {
     return kernel.GetCurrentProcess()->handle_table.Close(handle);
 }
 
+static ResultCode ReceiveIPCRequest(Kernel::KernelSystem& kernel, Memory::MemorySystem& memory,
+                                    std::shared_ptr<ServerSession> server_session,
+                                    std::shared_ptr<Thread> thread);
+
+class SVC_SyncCallback : public Kernel::WakeupCallback {
+public:
+    explicit SVC_SyncCallback(bool do_output_) : do_output(do_output_) {}
+    void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
+                std::shared_ptr<WaitObject> object) {
+
+        if (reason == ThreadWakeupReason::Timeout) {
+            thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
+            return;
+        }
+
+        ASSERT(reason == ThreadWakeupReason::Signal);
+
+        thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
+
+        // The wait_all case does not update the output index.
+        if (do_output) {
+            thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
+        }
+    }
+
+private:
+    bool do_output;
+
+    SVC_SyncCallback() = default;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::WakeupCallback>(*this);
+        ar& do_output;
+    }
+    friend class boost::serialization::access;
+};
+
+class SVC_IPCCallback : public Kernel::WakeupCallback {
+public:
+    explicit SVC_IPCCallback(Core::System& system_) : system(system_) {}
+
+    void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
+                std::shared_ptr<WaitObject> object) {
+
+        ASSERT(thread->status == ThreadStatus::WaitSynchAny);
+        ASSERT(reason == ThreadWakeupReason::Signal);
+
+        ResultCode result = RESULT_SUCCESS;
+
+        if (object->GetHandleType() == HandleType::ServerSession) {
+            auto server_session = DynamicObjectCast<ServerSession>(object);
+            result = ReceiveIPCRequest(system.Kernel(), system.Memory(), server_session, thread);
+        }
+
+        thread->SetWaitSynchronizationResult(result);
+        thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
+    }
+
+private:
+    Core::System& system;
+
+    SVC_IPCCallback() : system(Core::Global<Core::System>()) {}
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::WakeupCallback>(*this);
+    }
+    friend class boost::serialization::access;
+};
+
 /// Wait for a handle to synchronize, timeout after the specified nanoseconds
 ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
     auto object = kernel.GetCurrentProcess()->handle_table.Get<WaitObject>(handle);
@@ -426,21 +496,7 @@ ResultCode SVC::WaitSynchronization1(Handle handle, s64 nano_seconds) {
         // Create an event to wake the thread up after the specified nanosecond delay has passed
         thread->WakeAfterDelay(nano_seconds);
 
-        thread->wakeup_callback = [](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
-                                     std::shared_ptr<WaitObject> object) {
-            ASSERT(thread->status == ThreadStatus::WaitSynchAny);
-
-            if (reason == ThreadWakeupReason::Timeout) {
-                thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
-                return;
-            }
-
-            ASSERT(reason == ThreadWakeupReason::Signal);
-            thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
-
-            // WaitSynchronization1 doesn't have an output index like WaitSynchronizationN, so we
-            // don't have to do anything else here.
-        };
+        thread->wakeup_callback = std::make_shared<SVC_SyncCallback>(false);
 
         system.PrepareReschedule();
 
@@ -515,20 +571,7 @@ ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle
         // Create an event to wake the thread up after the specified nanosecond delay has passed
         thread->WakeAfterDelay(nano_seconds);
 
-        thread->wakeup_callback = [](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
-                                     std::shared_ptr<WaitObject> object) {
-            ASSERT(thread->status == ThreadStatus::WaitSynchAll);
-
-            if (reason == ThreadWakeupReason::Timeout) {
-                thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
-                return;
-            }
-
-            ASSERT(reason == ThreadWakeupReason::Signal);
-
-            thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
-            // The wait_all case does not update the output index.
-        };
+        thread->wakeup_callback = std::make_shared<SVC_SyncCallback>(false);
 
         system.PrepareReschedule();
 
@@ -575,20 +618,7 @@ ResultCode SVC::WaitSynchronizationN(s32* out, VAddr handles_address, s32 handle
         // Create an event to wake the thread up after the specified nanosecond delay has passed
         thread->WakeAfterDelay(nano_seconds);
 
-        thread->wakeup_callback = [](ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
-                                     std::shared_ptr<WaitObject> object) {
-            ASSERT(thread->status == ThreadStatus::WaitSynchAny);
-
-            if (reason == ThreadWakeupReason::Timeout) {
-                thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
-                return;
-            }
-
-            ASSERT(reason == ThreadWakeupReason::Signal);
-
-            thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
-            thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
-        };
+        thread->wakeup_callback = std::make_shared<SVC_SyncCallback>(true);
 
         system.PrepareReschedule();
 
@@ -730,22 +760,7 @@ ResultCode SVC::ReplyAndReceive(s32* index, VAddr handles_address, s32 handle_co
 
     thread->wait_objects = std::move(objects);
 
-    thread->wakeup_callback = [& kernel = this->kernel, &memory = this->memory](
-                                  ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
-                                  std::shared_ptr<WaitObject> object) {
-        ASSERT(thread->status == ThreadStatus::WaitSynchAny);
-        ASSERT(reason == ThreadWakeupReason::Signal);
-
-        ResultCode result = RESULT_SUCCESS;
-
-        if (object->GetHandleType() == HandleType::ServerSession) {
-            auto server_session = DynamicObjectCast<ServerSession>(object);
-            result = ReceiveIPCRequest(kernel, memory, server_session, thread);
-        }
-
-        thread->SetWaitSynchronizationResult(result);
-        thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get()));
-    };
+    thread->wakeup_callback = std::make_shared<SVC_IPCCallback>(system);
 
     system.PrepareReschedule();
 
@@ -916,7 +931,7 @@ ResultCode SVC::CreateThread(Handle* out_handle, u32 entry_point, u32 arg, VAddr
 
     CASCADE_RESULT(std::shared_ptr<Thread> thread,
                    kernel.CreateThread(name, entry_point, priority, arg, processor_id, stack_top,
-                                       *current_process));
+                                       current_process));
 
     thread->context->SetFpscr(FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO |
                               FPSCR_ROUND_TOZERO); // 0x03C00000
@@ -1025,7 +1040,7 @@ ResultCode SVC::GetProcessIdOfThread(u32* process_id, Handle thread_handle) {
     if (thread == nullptr)
         return ERR_INVALID_HANDLE;
 
-    const std::shared_ptr<Process> process = SharedFrom(thread->owner_process);
+    const std::shared_ptr<Process> process = thread->owner_process;
 
     ASSERT_MSG(process != nullptr, "Invalid parent process for thread={:#010X}", thread_handle);
 
@@ -1592,6 +1607,7 @@ void SVC::CallSVC(u32 immediate) {
                      "Running threads from exiting processes is unimplemented");
 
     const FunctionDef* info = GetSVCInfo(immediate);
+    LOG_TRACE(Kernel_SVC, "calling {}", info->name);
     if (info) {
         if (info->func) {
             (this->*(info->func))();
@@ -1619,3 +1635,6 @@ void SVCContext::CallSVC(u32 immediate) {
 }
 
 } // namespace Kernel
+
+SERIALIZE_EXPORT_IMPL(Kernel::SVC_SyncCallback)
+SERIALIZE_EXPORT_IMPL(Kernel::SVC_IPCCallback)
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index efddff9e8..4cf265400 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <memory>
+#include <boost/serialization/export.hpp>
 #include "common/common_types.h"
 
 namespace Core {
@@ -25,4 +26,10 @@ private:
     std::unique_ptr<SVC> impl;
 };
 
+class SVC_SyncCallback;
+class SVC_IPCCallback;
+
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::SVC_SyncCallback)
+BOOST_CLASS_EXPORT_KEY(Kernel::SVC_IPCCallback)
diff --git a/src/core/hle/kernel/svc_wrapper.h b/src/core/hle/kernel/svc_wrapper.h
index b4fbbe0de..77334152d 100644
--- a/src/core/hle/kernel/svc_wrapper.h
+++ b/src/core/hle/kernel/svc_wrapper.h
@@ -280,6 +280,26 @@ private:
         }
     };
 
+    template <typename SVCT>
+    struct WrapPass<SVCT, ResultCode /*empty for T, Ts...*/> {
+        // Call function R(Context::svc)(Us...) and transfer the return value to registers
+        template <typename... Us>
+        static void Call(Context& context, SVCT svc, Us... u) {
+            static_assert(std::is_same_v<SVCT, ResultCode (Context::*)(Us...)>);
+            if constexpr (std::is_void_v<ResultCode>) {
+                (context.*svc)(u...);
+            } else {
+                ResultCode r = (context.*svc)(u...);
+                if (r.IsError()) {
+                    LOG_ERROR(Kernel_SVC, "level={} summary={} module={} description={}",
+                              r.level.ExtractValue(r.raw), r.summary.ExtractValue(r.raw),
+                              r.module.ExtractValue(r.raw), r.description.ExtractValue(r.raw));
+                }
+                SetParam<INDEX_RETURN, ResultCode, ResultCode, Us...>(context, r);
+            }
+        }
+    };
+
     template <typename T>
     struct WrapHelper;
 
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index b2255c226..60d202631 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -6,10 +6,13 @@
 #include <list>
 #include <unordered_map>
 #include <vector>
+#include <boost/serialization/string.hpp>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/math_util.h"
+#include "common/serialization/boost_flat_set.h"
 #include "core/arm/arm_interface.h"
 #include "core/arm/skyeye_common/armstate.h"
 #include "core/core.h"
@@ -23,8 +26,34 @@
 #include "core/hle/result.h"
 #include "core/memory.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::Thread)
+
 namespace Kernel {
 
+template <class Archive>
+void Thread::serialize(Archive& ar, const unsigned int file_version) {
+    ar& boost::serialization::base_object<WaitObject>(*this);
+    ar&* context.get();
+    ar& thread_id;
+    ar& status;
+    ar& entry_point;
+    ar& stack_top;
+    ar& nominal_priority;
+    ar& current_priority;
+    ar& last_running_ticks;
+    ar& processor_id;
+    ar& tls_address;
+    ar& held_mutexes;
+    ar& pending_mutexes;
+    ar& owner_process;
+    ar& wait_objects;
+    ar& wait_address;
+    ar& name;
+    ar& wakeup_callback;
+}
+
+SERIALIZE_IMPL(Thread)
+
 bool Thread::ShouldWait(const Thread* thread) const {
     return status != ThreadStatus::Dead;
 }
@@ -34,7 +63,7 @@ void Thread::Acquire(Thread* thread) {
 }
 
 Thread::Thread(KernelSystem& kernel, u32 core_id)
-    : WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()),
+    : WaitObject(kernel), context(kernel.GetThreadManager(core_id).NewContext()), core_id(core_id),
       thread_manager(kernel.GetThreadManager(core_id)) {}
 Thread::~Thread() {}
 
@@ -75,7 +104,7 @@ void Thread::Stop() {
 
 void ThreadManager::SwitchContext(Thread* new_thread) {
     Thread* previous_thread = GetCurrentThread();
-    Process* previous_process = nullptr;
+    std::shared_ptr<Process> previous_process = nullptr;
 
     Core::Timing& timing = kernel.timing;
 
@@ -107,7 +136,7 @@ void ThreadManager::SwitchContext(Thread* new_thread) {
         new_thread->status = ThreadStatus::Running;
 
         if (previous_process != current_thread->owner_process) {
-            kernel.SetCurrentProcessForCPU(SharedFrom(current_thread->owner_process), cpu->GetID());
+            kernel.SetCurrentProcessForCPU(current_thread->owner_process, cpu->GetID());
         }
 
         cpu->LoadContext(new_thread->context);
@@ -164,7 +193,7 @@ void ThreadManager::ThreadWakeupCallback(u64 thread_id, s64 cycles_late) {
 
         // Invoke the wakeup callback before clearing the wait objects
         if (thread->wakeup_callback)
-            thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr);
+            thread->wakeup_callback->WakeUp(ThreadWakeupReason::Timeout, thread, nullptr);
 
         // Remove the thread from each of its waiting objects' waitlists
         for (auto& object : thread->wait_objects)
@@ -282,10 +311,9 @@ static void ResetThreadContext(const std::unique_ptr<ARM_Interface::ThreadContex
     context->SetCpsr(USER32MODE | ((entry_point & 1) << 5)); // Usermode and THUMB mode
 }
 
-ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(std::string name, VAddr entry_point,
-                                                              u32 priority, u32 arg,
-                                                              s32 processor_id, VAddr stack_top,
-                                                              Process& owner_process) {
+ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(
+    std::string name, VAddr entry_point, u32 priority, u32 arg, s32 processor_id, VAddr stack_top,
+    std::shared_ptr<Process> owner_process) {
     // Check if priority is in ranged. Lowest priority -> highest priority id.
     if (priority > ThreadPrioLowest) {
         LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
@@ -299,7 +327,7 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(std::string name,
 
     // TODO(yuriks): Other checks, returning 0xD9001BEA
 
-    if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) {
+    if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) {
         LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:08x}", name, entry_point);
         // TODO: Verify error
         return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,
@@ -322,17 +350,17 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(std::string name,
     thread->wait_address = 0;
     thread->name = std::move(name);
     thread_managers[processor_id]->wakeup_callback_table[thread->thread_id] = thread.get();
-    thread->owner_process = &owner_process;
+    thread->owner_process = owner_process;
 
     // Find the next available TLS index, and mark it as used
-    auto& tls_slots = owner_process.tls_slots;
+    auto& tls_slots = owner_process->tls_slots;
 
     auto [available_page, available_slot, needs_allocation] = GetFreeThreadLocalSlot(tls_slots);
 
     if (needs_allocation) {
         // There are no already-allocated pages with free slots, lets allocate a new one.
         // TLS pages are allocated from the BASE region in the linear heap.
-        MemoryRegionInfo* memory_region = GetMemoryRegion(MemoryRegion::BASE);
+        auto memory_region = GetMemoryRegion(MemoryRegion::BASE);
 
         // Allocate some memory from the end of the linear heap for this region.
         auto offset = memory_region->LinearAllocate(Memory::PAGE_SIZE);
@@ -341,17 +369,17 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(std::string name,
                       "Not enough space in region to allocate a new TLS page for thread");
             return ERR_OUT_OF_MEMORY;
         }
-        owner_process.memory_used += Memory::PAGE_SIZE;
+        owner_process->memory_used += Memory::PAGE_SIZE;
 
         tls_slots.emplace_back(0); // The page is completely available at the start
         available_page = tls_slots.size() - 1;
         available_slot = 0; // Use the first slot in the new page
 
-        auto& vm_manager = owner_process.vm_manager;
+        auto& vm_manager = owner_process->vm_manager;
 
         // Map the page to the current process' address space.
         vm_manager.MapBackingMemory(Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE,
-                                    memory.GetFCRAMPointer(*offset), Memory::PAGE_SIZE,
+                                    memory.GetFCRAMRef(*offset), Memory::PAGE_SIZE,
                                     MemoryState::Locked);
     }
 
@@ -360,7 +388,7 @@ ResultVal<std::shared_ptr<Thread>> KernelSystem::CreateThread(std::string name,
     thread->tls_address = Memory::TLS_AREA_VADDR + available_page * Memory::PAGE_SIZE +
                           available_slot * Memory::TLS_ENTRY_SIZE;
 
-    memory.ZeroBlock(owner_process, thread->tls_address, Memory::TLS_ENTRY_SIZE);
+    memory.ZeroBlock(*owner_process, thread->tls_address, Memory::TLS_ENTRY_SIZE);
 
     // TODO(peachum): move to ScheduleThread() when scheduler is added so selected core is used
     // to initialize the context
@@ -407,7 +435,7 @@ std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u
     // Initialize new "main" thread
     auto thread_res =
         kernel.CreateThread("main", entry_point, priority, 0, owner_process->ideal_processor,
-                            Memory::HEAP_VADDR_END, *owner_process);
+                            Memory::HEAP_VADDR_END, owner_process);
 
     std::shared_ptr<Thread> thread = std::move(thread_res).Unwrap();
 
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index cd47448a2..233381f67 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -9,6 +9,10 @@
 #include <unordered_map>
 #include <vector>
 #include <boost/container/flat_set.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unordered_map.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
 #include "common/thread_queue_list.h"
 #include "core/arm/arm_interface.h"
@@ -57,6 +61,20 @@ enum class ThreadWakeupReason {
     Timeout // The thread was woken up due to a wait timeout.
 };
 
+class Thread;
+
+class WakeupCallback {
+public:
+    virtual ~WakeupCallback() = default;
+    virtual void WakeUp(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
+                        std::shared_ptr<WaitObject> object) = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {}
+    friend class boost::serialization::access;
+};
+
 class ThreadManager {
 public:
     explicit ThreadManager(Kernel::KernelSystem& kernel, u32 core_id);
@@ -140,6 +158,15 @@ private:
 
     friend class Thread;
     friend class KernelSystem;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& current_thread;
+        ar& ready_queue;
+        ar& wakeup_callback_table;
+        ar& thread_list;
+    }
 };
 
 class Thread final : public WaitObject {
@@ -276,30 +303,34 @@ public:
     VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
 
     /// Mutexes currently held by this thread, which will be released when it exits.
-    boost::container::flat_set<std::shared_ptr<Mutex>> held_mutexes;
+    boost::container::flat_set<std::shared_ptr<Mutex>> held_mutexes{};
 
     /// Mutexes that this thread is currently waiting for.
-    boost::container::flat_set<std::shared_ptr<Mutex>> pending_mutexes;
+    boost::container::flat_set<std::shared_ptr<Mutex>> pending_mutexes{};
 
-    Process* owner_process; ///< Process that owns this thread
+    std::shared_ptr<Process> owner_process{}; ///< Process that owns this thread
 
     /// Objects that the thread is waiting on, in the same order as they were
     // passed to WaitSynchronization1/N.
-    std::vector<std::shared_ptr<WaitObject>> wait_objects;
+    std::vector<std::shared_ptr<WaitObject>> wait_objects{};
 
     VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
 
-    std::string name;
+    std::string name{};
 
-    using WakeupCallback = void(ThreadWakeupReason reason, std::shared_ptr<Thread> thread,
-                                std::shared_ptr<WaitObject> object);
     // Callback that will be invoked when the thread is resumed from a waiting state. If the thread
     // was waiting via WaitSynchronizationN then the object will be the last object that became
     // available. In case of a timeout, the object will be nullptr.
-    std::function<WakeupCallback> wakeup_callback;
+    std::shared_ptr<WakeupCallback> wakeup_callback{};
+
+    const u32 core_id;
 
 private:
     ThreadManager& thread_manager;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 
 /**
@@ -314,3 +345,22 @@ std::shared_ptr<Thread> SetupMainThread(KernelSystem& kernel, u32 entry_point, u
                                         std::shared_ptr<Process> owner_process);
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::Thread)
+
+namespace boost::serialization {
+
+template <class Archive>
+inline void save_construct_data(Archive& ar, const Kernel::Thread* t,
+                                const unsigned int file_version) {
+    ar << t->core_id;
+}
+
+template <class Archive>
+inline void load_construct_data(Archive& ar, Kernel::Thread* t, const unsigned int file_version) {
+    u32 core_id;
+    ar >> core_id;
+    ::new (t) Kernel::Thread(Core::Global<Kernel::KernelSystem>(), core_id);
+}
+
+} // namespace boost::serialization
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 9c3d0f725..d2a12ade1 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -4,6 +4,7 @@
 
 #include <cinttypes>
 #include <unordered_map>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -12,6 +13,8 @@
 #include "core/hle/kernel/thread.h"
 #include "core/hle/kernel/timer.h"
 
+SERIALIZE_EXPORT_IMPL(Kernel::Timer)
+
 namespace Kernel {
 
 Timer::Timer(KernelSystem& kernel)
diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h
index 6865f5243..d5af5e654 100644
--- a/src/core/hle/kernel/timer.h
+++ b/src/core/hle/kernel/timer.h
@@ -4,6 +4,8 @@
 
 #pragma once
 
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/unordered_map.hpp>
 #include "common/common_types.h"
 #include "core/core_timing.h"
 #include "core/hle/kernel/object.h"
@@ -33,6 +35,13 @@ private:
 
     friend class Timer;
     friend class KernelSystem;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& next_timer_callback_id;
+        ar& timer_callback_table;
+    }
 };
 
 class Timer final : public WaitObject {
@@ -103,6 +112,21 @@ private:
     TimerManager& timer_manager;
 
     friend class KernelSystem;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<WaitObject>(*this);
+        ar& reset_type;
+        ar& initial_delay;
+        ar& interval_delay;
+        ar& signaled;
+        ar& name;
+        ar& callback_id;
+    }
 };
 
 } // namespace Kernel
+
+BOOST_CLASS_EXPORT_KEY(Kernel::Timer)
+CONSTRUCT_KERNEL_OBJECT(Kernel::Timer)
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index ba2e2bd1a..83541b2b2 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -27,7 +27,8 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
         type != next.type) {
         return false;
     }
-    if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) {
+    if (type == VMAType::BackingMemory &&
+        backing_memory.GetPtr() + size != next.backing_memory.GetPtr()) {
         return false;
     }
     if (type == VMAType::MMIO && paddr + size != next.paddr) {
@@ -36,7 +37,8 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
     return true;
 }
 
-VMManager::VMManager(Memory::MemorySystem& memory) : memory(memory) {
+VMManager::VMManager(Memory::MemorySystem& memory)
+    : memory(memory), page_table(std::make_shared<Memory::PageTable>()) {
     Reset();
 }
 
@@ -50,8 +52,7 @@ void VMManager::Reset() {
     initial_vma.size = MAX_ADDRESS;
     vma_map.emplace(initial_vma.base, initial_vma);
 
-    page_table.pointers.fill(nullptr);
-    page_table.attributes.fill(Memory::PageType::Unmapped);
+    page_table->Clear();
 
     UpdatePageTableForVMA(initial_vma);
 }
@@ -64,7 +65,7 @@ VMManager::VMAHandle VMManager::FindVMA(VAddr target) const {
     }
 }
 
-ResultVal<VAddr> VMManager::MapBackingMemoryToBase(VAddr base, u32 region_size, u8* memory,
+ResultVal<VAddr> VMManager::MapBackingMemoryToBase(VAddr base, u32 region_size, MemoryRef memory,
                                                    u32 size, MemoryState state) {
 
     // Find the first Free VMA.
@@ -93,9 +94,9 @@ ResultVal<VAddr> VMManager::MapBackingMemoryToBase(VAddr base, u32 region_size,
     return MakeResult<VAddr>(target);
 }
 
-ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* memory, u32 size,
-                                                            MemoryState state) {
-    ASSERT(memory != nullptr);
+ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, MemoryRef memory,
+                                                            u32 size, MemoryState state) {
+    ASSERT(memory.GetPtr() != nullptr);
 
     // This is the appropriately sized VMA that will turn into our allocation.
     CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
@@ -351,20 +352,20 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
 void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
     switch (vma.type) {
     case VMAType::Free:
-        memory.UnmapRegion(page_table, vma.base, vma.size);
+        memory.UnmapRegion(*page_table, vma.base, vma.size);
         break;
     case VMAType::BackingMemory:
-        memory.MapMemoryRegion(page_table, vma.base, vma.size, vma.backing_memory);
+        memory.MapMemoryRegion(*page_table, vma.base, vma.size, vma.backing_memory);
         break;
     case VMAType::MMIO:
-        memory.MapIoRegion(page_table, vma.base, vma.size, vma.mmio_handler);
+        memory.MapIoRegion(*page_table, vma.base, vma.size, vma.mmio_handler);
         break;
     }
 }
 
-ResultVal<std::vector<std::pair<u8*, u32>>> VMManager::GetBackingBlocksForRange(VAddr address,
-                                                                                u32 size) {
-    std::vector<std::pair<u8*, u32>> backing_blocks;
+ResultVal<std::vector<std::pair<MemoryRef, u32>>> VMManager::GetBackingBlocksForRange(VAddr address,
+                                                                                      u32 size) {
+    std::vector<std::pair<MemoryRef, u32>> backing_blocks;
     VAddr interval_target = address;
     while (interval_target != address + size) {
         auto vma = FindVMA(interval_target);
@@ -375,7 +376,7 @@ ResultVal<std::vector<std::pair<u8*, u32>>> VMManager::GetBackingBlocksForRange(
 
         VAddr interval_end = std::min(address + size, vma->second.base + vma->second.size);
         u32 interval_size = interval_end - interval_target;
-        u8* backing_memory = vma->second.backing_memory + (interval_target - vma->second.base);
+        auto backing_memory = vma->second.backing_memory + (interval_target - vma->second.base);
         backing_blocks.push_back({backing_memory, interval_size});
 
         interval_target += interval_size;
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index fbd9bf09b..06fbb8672 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -8,7 +8,11 @@
 #include <memory>
 #include <utility>
 #include <vector>
+#include <boost/serialization/map.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/split_member.hpp>
 #include "common/common_types.h"
+#include "common/memory_ref.h"
 #include "core/hle/result.h"
 #include "core/memory.h"
 #include "core/mmio.h"
@@ -71,7 +75,7 @@ struct VirtualMemoryArea {
 
     // Settings for type = BackingMemory
     /// Pointer backing this VMA. It will not be destroyed or freed when the VMA is removed.
-    u8* backing_memory = nullptr;
+    MemoryRef backing_memory{};
 
     // Settings for type = MMIO
     /// Physical address of the register area this VMA maps to.
@@ -80,6 +84,20 @@ struct VirtualMemoryArea {
 
     /// Tests if this area can be merged to the right with `next`.
     bool CanBeMergedWith(const VirtualMemoryArea& next) const;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& base;
+        ar& size;
+        ar& type;
+        ar& permissions;
+        ar& meminfo_state;
+        ar& backing_memory;
+        ar& paddr;
+        ar& mmio_handler;
+    }
 };
 
 /**
@@ -134,7 +152,7 @@ public:
      * @param state MemoryState tag to attach to the VMA.
      * @returns The address at which the memory was mapped.
      */
-    ResultVal<VAddr> MapBackingMemoryToBase(VAddr base, u32 region_size, u8* memory, u32 size,
+    ResultVal<VAddr> MapBackingMemoryToBase(VAddr base, u32 region_size, MemoryRef memory, u32 size,
                                             MemoryState state);
     /**
      * Maps an unmanaged host memory pointer at a given address.
@@ -144,7 +162,8 @@ public:
      * @param size Size of the mapping.
      * @param state MemoryState tag to attach to the VMA.
      */
-    ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u32 size, MemoryState state);
+    ResultVal<VMAHandle> MapBackingMemory(VAddr target, MemoryRef memory, u32 size,
+                                          MemoryState state);
 
     /**
      * Maps a memory-mapped IO region at a given address.
@@ -186,11 +205,12 @@ public:
     void LogLayout(Log::Level log_level) const;
 
     /// Gets a list of backing memory blocks for the specified range
-    ResultVal<std::vector<std::pair<u8*, u32>>> GetBackingBlocksForRange(VAddr address, u32 size);
+    ResultVal<std::vector<std::pair<MemoryRef, u32>>> GetBackingBlocksForRange(VAddr address,
+                                                                               u32 size);
 
     /// Each VMManager has its own page table, which is set as the main one when the owning process
     /// is scheduled.
-    Memory::PageTable page_table;
+    std::shared_ptr<Memory::PageTable> page_table;
 
 private:
     using VMAIter = decltype(vma_map)::iterator;
@@ -229,5 +249,12 @@ private:
     void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
 
     Memory::MemorySystem& memory;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& vma_map;
+        ar& page_table;
+    }
+    friend class boost::serialization::access;
 };
 } // namespace Kernel
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp
index 94ae632cd..2cd4331c5 100644
--- a/src/core/hle/kernel/wait_object.cpp
+++ b/src/core/hle/kernel/wait_object.cpp
@@ -4,6 +4,7 @@
 
 #include <algorithm>
 #include <utility>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "core/hle/kernel/errors.h"
@@ -16,6 +17,15 @@
 
 namespace Kernel {
 
+template <class Archive>
+void WaitObject::serialize(Archive& ar, const unsigned int file_version) {
+    ar& boost::serialization::base_object<Object>(*this);
+    ar& waiting_threads;
+    // NB: hle_notifier *not* serialized since it's a callback!
+    // Fortunately it's only used in one place (DSP) so we can reconstruct it there
+}
+SERIALIZE_IMPL(WaitObject)
+
 void WaitObject::AddWaitingThread(std::shared_ptr<Thread> thread) {
     auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
     if (itr == waiting_threads.end())
@@ -80,7 +90,7 @@ void WaitObject::WakeupAllWaitingThreads() {
 
         // Invoke the wakeup callback before clearing the wait objects
         if (thread->wakeup_callback)
-            thread->wakeup_callback(ThreadWakeupReason::Signal, thread, SharedFrom(this));
+            thread->wakeup_callback->WakeUp(ThreadWakeupReason::Signal, thread, SharedFrom(this));
 
         for (auto& object : thread->wait_objects)
             object->RemoveWaitingThread(thread.get());
diff --git a/src/core/hle/kernel/wait_object.h b/src/core/hle/kernel/wait_object.h
index 41e803515..7f73eab9f 100644
--- a/src/core/hle/kernel/wait_object.h
+++ b/src/core/hle/kernel/wait_object.h
@@ -7,6 +7,9 @@
 #include <functional>
 #include <memory>
 #include <vector>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
 #include "core/hle/kernel/object.h"
 
@@ -62,6 +65,11 @@ private:
 
     /// Function to call when this object becomes available
     std::function<void()> hle_notifier;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
 };
 
 // Specialization of DynamicObjectCast for WaitObjects
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 1543d7bd8..40a71e369 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -6,6 +6,7 @@
 
 #include <new>
 #include <utility>
+#include <boost/serialization/access.hpp>
 #include "common/assert.h"
 #include "common/bit_field.h"
 #include "common/common_funcs.h"
@@ -225,6 +226,13 @@ union ResultCode {
     constexpr bool IsError() const {
         return is_error.ExtractValue(raw) == 1;
     }
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& raw;
+    }
+    friend class boost::serialization::access;
 };
 
 constexpr bool operator==(const ResultCode& a, const ResultCode& b) {
diff --git a/src/core/hle/service/ac/ac.cpp b/src/core/hle/service/ac/ac.cpp
index 9d40b9661..d9f1085c8 100644
--- a/src/core/hle/service/ac/ac.cpp
+++ b/src/core/hle/service/ac/ac.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <vector>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -179,4 +180,15 @@ void InstallInterfaces(Core::System& system) {
     std::make_shared<AC_U>(ac)->InstallAsService(service_manager);
 }
 
+template <class Archive>
+void Module::serialize(Archive& ar, const unsigned int) {
+    ar& ac_connected;
+    ar& close_event;
+    ar& connect_event;
+    ar& disconnect_event;
+    // default_config is never written to
+}
+
 } // namespace Service::AC
+
+SERIALIZE_IMPL(Service::AC::Module)
diff --git a/src/core/hle/service/ac/ac.h b/src/core/hle/service/ac/ac.h
index f3554c876..e4342bde9 100644
--- a/src/core/hle/service/ac/ac.h
+++ b/src/core/hle/service/ac/ac.h
@@ -153,6 +153,11 @@ protected:
     std::shared_ptr<Kernel::Event> close_event;
     std::shared_ptr<Kernel::Event> connect_event;
     std::shared_ptr<Kernel::Event> disconnect_event;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
diff --git a/src/core/hle/service/ac/ac_i.cpp b/src/core/hle/service/ac/ac_i.cpp
index 0dde7bf90..ff4fb954c 100644
--- a/src/core/hle/service/ac/ac_i.cpp
+++ b/src/core/hle/service/ac/ac_i.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ac/ac_i.h"
 
 namespace Service::AC {
@@ -33,3 +34,5 @@ AC_I::AC_I(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:i"
 }
 
 } // namespace Service::AC
+
+SERIALIZE_EXPORT_IMPL(Service::AC::AC_I)
diff --git a/src/core/hle/service/ac/ac_i.h b/src/core/hle/service/ac/ac_i.h
index bca91aabe..8e852fb9b 100644
--- a/src/core/hle/service/ac/ac_i.h
+++ b/src/core/hle/service/ac/ac_i.h
@@ -12,6 +12,12 @@ namespace Service::AC {
 class AC_I final : public Module::Interface {
 public:
     explicit AC_I(std::shared_ptr<Module> ac);
+
+private:
+    SERVICE_SERIALIZATION(AC_I, ac, Module)
 };
 
 } // namespace Service::AC
+
+BOOST_CLASS_EXPORT_KEY(Service::AC::AC_I)
+BOOST_SERIALIZATION_CONSTRUCT(Service::AC::AC_I)
diff --git a/src/core/hle/service/ac/ac_u.cpp b/src/core/hle/service/ac/ac_u.cpp
index d62d7ccb6..e88cdd164 100644
--- a/src/core/hle/service/ac/ac_u.cpp
+++ b/src/core/hle/service/ac/ac_u.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ac/ac_u.h"
 
 namespace Service::AC {
@@ -33,3 +34,5 @@ AC_U::AC_U(std::shared_ptr<Module> ac) : Module::Interface(std::move(ac), "ac:u"
 }
 
 } // namespace Service::AC
+
+SERIALIZE_EXPORT_IMPL(Service::AC::AC_U)
diff --git a/src/core/hle/service/ac/ac_u.h b/src/core/hle/service/ac/ac_u.h
index 18efcd1e6..117269f1f 100644
--- a/src/core/hle/service/ac/ac_u.h
+++ b/src/core/hle/service/ac/ac_u.h
@@ -12,6 +12,12 @@ namespace Service::AC {
 class AC_U final : public Module::Interface {
 public:
     explicit AC_U(std::shared_ptr<Module> ac);
+
+private:
+    SERVICE_SERIALIZATION(AC_U, ac, Module)
 };
 
 } // namespace Service::AC
+
+BOOST_CLASS_EXPORT_KEY(Service::AC::AC_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::AC::AC_U)
diff --git a/src/core/hle/service/act/act.h b/src/core/hle/service/act/act.h
index 884678890..e5c2cf7ae 100644
--- a/src/core/hle/service/act/act.h
+++ b/src/core/hle/service/act/act.h
@@ -20,9 +20,14 @@ public:
         Interface(std::shared_ptr<Module> act, const char* name);
         ~Interface();
 
-    private:
+    protected:
         std::shared_ptr<Module> act;
     };
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {}
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
diff --git a/src/core/hle/service/act/act_a.cpp b/src/core/hle/service/act/act_a.cpp
index 7a33f9175..b85a17183 100644
--- a/src/core/hle/service/act/act_a.cpp
+++ b/src/core/hle/service/act/act_a.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/act/act_a.h"
 
 namespace Service::ACT {
@@ -24,3 +25,5 @@ ACT_A::ACT_A(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "a
 }
 
 } // namespace Service::ACT
+
+SERIALIZE_EXPORT_IMPL(Service::ACT::ACT_A)
diff --git a/src/core/hle/service/act/act_a.h b/src/core/hle/service/act/act_a.h
index 48a79aab7..1454441d5 100644
--- a/src/core/hle/service/act/act_a.h
+++ b/src/core/hle/service/act/act_a.h
@@ -11,6 +11,12 @@ namespace Service::ACT {
 class ACT_A final : public Module::Interface {
 public:
     explicit ACT_A(std::shared_ptr<Module> act);
+
+private:
+    SERVICE_SERIALIZATION(ACT_A, act, Module)
 };
 
 } // namespace Service::ACT
+
+BOOST_CLASS_EXPORT_KEY(Service::ACT::ACT_A)
+BOOST_SERIALIZATION_CONSTRUCT(Service::ACT::ACT_A)
diff --git a/src/core/hle/service/act/act_u.cpp b/src/core/hle/service/act/act_u.cpp
index 99978d9ca..a0058d573 100644
--- a/src/core/hle/service/act/act_u.cpp
+++ b/src/core/hle/service/act/act_u.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/act/act_u.h"
 
 namespace Service::ACT {
@@ -20,3 +21,5 @@ ACT_U::ACT_U(std::shared_ptr<Module> act) : Module::Interface(std::move(act), "a
 }
 
 } // namespace Service::ACT
+
+SERIALIZE_EXPORT_IMPL(Service::ACT::ACT_U)
diff --git a/src/core/hle/service/act/act_u.h b/src/core/hle/service/act/act_u.h
index 3aca428e6..bcd2d653e 100644
--- a/src/core/hle/service/act/act_u.h
+++ b/src/core/hle/service/act/act_u.h
@@ -11,6 +11,12 @@ namespace Service::ACT {
 class ACT_U final : public Module::Interface {
 public:
     explicit ACT_U(std::shared_ptr<Module> act);
+
+private:
+    SERVICE_SERIALIZATION(ACT_U, act, Module)
 };
 
 } // namespace Service::ACT
+
+BOOST_CLASS_EXPORT_KEY(Service::ACT::ACT_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::ACT::ACT_U)
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 31df828e3..240762d25 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -1053,7 +1053,7 @@ void Module::Interface::BeginImportProgram(Kernel::HLERequestContext& ctx) {
     // Citra will store contents out to sdmc/nand
     const FileSys::Path cia_path = {};
     auto file = std::make_shared<Service::FS::File>(
-        am->system, std::make_unique<CIAFile>(media_type), cia_path);
+        am->kernel, std::make_unique<CIAFile>(media_type), cia_path);
 
     am->cia_installing = true;
 
@@ -1080,7 +1080,7 @@ void Module::Interface::BeginImportProgramTemporarily(Kernel::HLERequestContext&
     // contents out to sdmc/nand
     const FileSys::Path cia_path = {};
     auto file = std::make_shared<Service::FS::File>(
-        am->system, std::make_unique<CIAFile>(FS::MediaType::NAND), cia_path);
+        am->kernel, std::make_unique<CIAFile>(FS::MediaType::NAND), cia_path);
 
     am->cia_installing = true;
 
@@ -1482,11 +1482,13 @@ void Module::Interface::GetMetaDataFromCia(Kernel::HLERequestContext& ctx) {
     rb.PushMappedBuffer(output_buffer);
 }
 
-Module::Module(Core::System& system) : system(system) {
+Module::Module(Core::System& system) : kernel(system.Kernel()) {
     ScanForAllTitles();
     system_updater_mutex = system.Kernel().CreateMutex(false, "AM::SystemUpdaterMutex");
 }
 
+Module::Module(Kernel::KernelSystem& kernel) : kernel(kernel) {}
+
 Module::~Module() = default;
 
 void InstallInterfaces(Core::System& system) {
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 0912dde40..25bd58265 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -9,9 +9,14 @@
 #include <memory>
 #include <string>
 #include <vector>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
+#include "common/construct.h"
 #include "core/file_sys/cia_container.h"
 #include "core/file_sys/file_backend.h"
+#include "core/global.h"
 #include "core/hle/kernel/mutex.h"
 #include "core/hle/result.h"
 #include "core/hle/service/service.h"
@@ -557,11 +562,13 @@ public:
          */
         void GetMetaDataFromCia(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> am;
     };
 
 private:
+    explicit Module(Kernel::KernelSystem& kernel);
+
     /**
      * Scans the for titles in a storage medium for listing.
      * @param media_type the storage medium to scan
@@ -573,12 +580,32 @@ private:
      */
     void ScanForAllTitles();
 
-    Core::System& system;
+    Kernel::KernelSystem& kernel;
     bool cia_installing = false;
     std::array<std::vector<u64_le>, 3> am_title_list;
     std::shared_ptr<Kernel::Mutex> system_updater_mutex;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& cia_installing;
+        ar& am_title_list;
+        ar& system_updater_mutex;
+    }
+
+    template <class Archive>
+    static void load_construct(Archive& ar, Module* t, const unsigned int file_version) {
+        ::new (t) Module(Core::Global<Kernel::KernelSystem>());
+    }
+
+    template <class Archive>
+    void save_construct(Archive& ar, const unsigned int file_version) const {}
+
+    friend class ::construct_access;
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::AM
+
+BOOST_SERIALIZATION_CONSTRUCT(Service::AM::Module);
diff --git a/src/core/hle/service/am/am_app.cpp b/src/core/hle/service/am/am_app.cpp
index cee1aa81b..788ee090c 100644
--- a/src/core/hle/service/am/am_app.cpp
+++ b/src/core/hle/service/am/am_app.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/am/am_app.h"
 
 namespace Service::AM {
@@ -26,3 +27,5 @@ AM_APP::AM_APP(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
 }
 
 } // namespace Service::AM
+
+SERIALIZE_EXPORT_IMPL(Service::AM::AM_APP)
diff --git a/src/core/hle/service/am/am_app.h b/src/core/hle/service/am/am_app.h
index 67cf8ba2e..b8e858cb4 100644
--- a/src/core/hle/service/am/am_app.h
+++ b/src/core/hle/service/am/am_app.h
@@ -11,6 +11,12 @@ namespace Service::AM {
 class AM_APP final : public Module::Interface {
 public:
     explicit AM_APP(std::shared_ptr<Module> am);
+
+private:
+    SERVICE_SERIALIZATION(AM_APP, am, Module)
 };
 
 } // namespace Service::AM
+
+BOOST_CLASS_EXPORT_KEY(Service::AM::AM_APP)
+BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_APP)
diff --git a/src/core/hle/service/am/am_net.cpp b/src/core/hle/service/am/am_net.cpp
index 120ee53e7..cd88965af 100644
--- a/src/core/hle/service/am/am_net.cpp
+++ b/src/core/hle/service/am/am_net.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/am/am_net.h"
 
 namespace Service::AM {
@@ -123,3 +124,5 @@ AM_NET::AM_NET(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
 }
 
 } // namespace Service::AM
+
+SERIALIZE_EXPORT_IMPL(Service::AM::AM_NET)
diff --git a/src/core/hle/service/am/am_net.h b/src/core/hle/service/am/am_net.h
index a5adbd7e5..b73610df9 100644
--- a/src/core/hle/service/am/am_net.h
+++ b/src/core/hle/service/am/am_net.h
@@ -11,6 +11,12 @@ namespace Service::AM {
 class AM_NET final : public Module::Interface {
 public:
     explicit AM_NET(std::shared_ptr<Module> am);
+
+private:
+    SERVICE_SERIALIZATION(AM_NET, am, Module)
 };
 
 } // namespace Service::AM
+
+BOOST_CLASS_EXPORT_KEY(Service::AM::AM_NET)
+BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_NET)
diff --git a/src/core/hle/service/am/am_sys.cpp b/src/core/hle/service/am/am_sys.cpp
index ca4affbc2..6c7e99235 100644
--- a/src/core/hle/service/am/am_sys.cpp
+++ b/src/core/hle/service/am/am_sys.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/am/am_sys.h"
 
 namespace Service::AM {
@@ -71,3 +72,5 @@ AM_SYS::AM_SYS(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "a
 }
 
 } // namespace Service::AM
+
+SERIALIZE_EXPORT_IMPL(Service::AM::AM_SYS)
diff --git a/src/core/hle/service/am/am_sys.h b/src/core/hle/service/am/am_sys.h
index b142916ca..fdee63b13 100644
--- a/src/core/hle/service/am/am_sys.h
+++ b/src/core/hle/service/am/am_sys.h
@@ -11,6 +11,12 @@ namespace Service::AM {
 class AM_SYS final : public Module::Interface {
 public:
     explicit AM_SYS(std::shared_ptr<Module> am);
+
+private:
+    SERVICE_SERIALIZATION(AM_SYS, am, Module)
 };
 
 } // namespace Service::AM
+
+BOOST_CLASS_EXPORT_KEY(Service::AM::AM_SYS)
+BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_SYS)
diff --git a/src/core/hle/service/am/am_u.cpp b/src/core/hle/service/am/am_u.cpp
index 840860ec0..cffb88388 100644
--- a/src/core/hle/service/am/am_u.cpp
+++ b/src/core/hle/service/am/am_u.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/am/am_u.h"
 
 namespace Service::AM {
@@ -83,3 +84,5 @@ AM_U::AM_U(std::shared_ptr<Module> am) : Module::Interface(std::move(am), "am:u"
 }
 
 } // namespace Service::AM
+
+SERIALIZE_EXPORT_IMPL(Service::AM::AM_U)
diff --git a/src/core/hle/service/am/am_u.h b/src/core/hle/service/am/am_u.h
index 1d732c90a..d71bf722c 100644
--- a/src/core/hle/service/am/am_u.h
+++ b/src/core/hle/service/am/am_u.h
@@ -11,6 +11,12 @@ namespace Service::AM {
 class AM_U final : public Module::Interface {
 public:
     explicit AM_U(std::shared_ptr<Module> am);
+
+private:
+    SERVICE_SERIALIZATION(AM_U, am, Module)
 };
 
 } // namespace Service::AM
+
+BOOST_CLASS_EXPORT_KEY(Service::AM::AM_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::AM::AM_U)
diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp
index 8a24595ff..6612a720e 100644
--- a/src/core/hle/service/apt/applet_manager.cpp
+++ b/src/core/hle/service/apt/applet_manager.cpp
@@ -10,6 +10,8 @@
 #include "core/hle/service/apt/ns.h"
 #include "core/hle/service/cfg/cfg.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::APT::AppletManager)
+
 namespace Service::APT {
 
 enum class AppletPos { Application = 0, Library = 1, System = 2, SysLibrary = 3, Resident = 4 };
diff --git a/src/core/hle/service/apt/applet_manager.h b/src/core/hle/service/apt/applet_manager.h
index 6bc880bd4..0f1a46a8b 100644
--- a/src/core/hle/service/apt/applet_manager.h
+++ b/src/core/hle/service/apt/applet_manager.h
@@ -6,8 +6,12 @@
 
 #include <array>
 #include <memory>
-#include <optional>
 #include <vector>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/optional.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/vector.hpp>
+#include "core/global.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/result.h"
 #include "core/hle/service/fs/archive.h"
@@ -84,6 +88,17 @@ struct MessageParameter {
     SignalType signal = SignalType::None;
     std::shared_ptr<Kernel::Object> object = nullptr;
     std::vector<u8> buffer;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& sender_id;
+        ar& destination_id;
+        ar& signal;
+        ar& object;
+        ar& buffer;
+    }
+    friend class boost::serialization::access;
 };
 
 /// Holds information about the parameters used in StartLibraryApplet
@@ -161,6 +176,16 @@ public:
 
         u64 current_title_id;
         FS::MediaType current_media_type;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& next_title_id;
+            ar& next_media_type;
+            ar& current_title_id;
+            ar& current_media_type;
+        }
+        friend class boost::serialization::access;
     };
 
     ApplicationJumpParameters GetApplicationJumpParameters() const {
@@ -169,7 +194,8 @@ public:
 
 private:
     /// Parameter data to be returned in the next call to Glance/ReceiveParameter.
-    std::optional<MessageParameter> next_parameter;
+    // NOTE: A bug in gcc prevents serializing std::optional
+    boost::optional<MessageParameter> next_parameter;
 
     static constexpr std::size_t NumAppletSlot = 4;
 
@@ -199,6 +225,20 @@ private:
             title_id = 0;
             attributes.raw = 0;
         }
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& applet_id;
+            ar& slot;
+            ar& title_id;
+            ar& registered;
+            ar& loaded;
+            ar& attributes.raw;
+            ar& notification_event;
+            ar& parameter_event;
+        }
+        friend class boost::serialization::access;
     };
 
     ApplicationJumpParameters app_jump_parameters{};
@@ -216,6 +256,18 @@ private:
     SignalType library_applet_closing_command;
 
     Core::System& system;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& next_parameter;
+        ar& app_jump_parameters;
+        ar& applet_slots;
+        ar& library_applet_closing_command;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::APT
+
+SERVICE_CONSTRUCT(Service::APT::AppletManager)
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index f7fb0660f..d93b4bcb2 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -2,6 +2,9 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/vector.hpp>
+#include "common/archives.h"
 #include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -26,8 +29,25 @@
 #include "core/hw/aes/ccm.h"
 #include "core/hw/aes/key.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::APT::Module)
+
 namespace Service::APT {
 
+template <class Archive>
+void Module::serialize(Archive& ar, const unsigned int) {
+    ar& shared_font_mem;
+    ar& shared_font_loaded;
+    ar& shared_font_relocated;
+    ar& lock;
+    ar& cpu_percent;
+    ar& unknown_ns_state_field;
+    ar& screen_capture_buffer;
+    ar& screen_capture_post_permission;
+    ar& applet_manager;
+}
+
+SERIALIZE_IMPL(Module)
+
 Module::NSInterface::NSInterface(std::shared_ptr<Module> apt, const char* name, u32 max_session)
     : ServiceFramework(name, max_session), apt(std::move(apt)) {}
 
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index 2e2c219cd..8b01d3b8e 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -6,9 +6,11 @@
 
 #include <memory>
 #include <vector>
+#include "common/archives.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
 #include "common/swap.h"
+#include "core/global.h"
 #include "core/hle/kernel/kernel.h"
 #include "core/hle/service/service.h"
 
@@ -65,7 +67,7 @@ public:
         NSInterface(std::shared_ptr<Module> apt, const char* name, u32 max_session);
         ~NSInterface();
 
-    private:
+    protected:
         std::shared_ptr<Module> apt;
     };
 
@@ -601,9 +603,16 @@ public:
          */
         void CheckNew3DS(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         bool application_reset_prepared{};
         std::shared_ptr<Module> apt;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& application_reset_prepared;
+        }
+        friend class boost::serialization::access;
     };
 
 private:
@@ -630,8 +639,14 @@ private:
         ScreencapPostPermission::CleanThePermission; // TODO(JamePeng): verify the initial value
 
     std::shared_ptr<AppletManager> applet_manager;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::APT
+
+SERVICE_CONSTRUCT(Service::APT::Module)
diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp
index 7d5c96139..0d6e64d30 100644
--- a/src/core/hle/service/apt/apt_a.cpp
+++ b/src/core/hle/service/apt/apt_a.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/apt/apt_a.h"
 
 namespace Service::APT {
@@ -105,3 +106,5 @@ APT_A::APT_A(std::shared_ptr<Module> apt)
 }
 
 } // namespace Service::APT
+
+SERIALIZE_EXPORT_IMPL(Service::APT::APT_A)
diff --git a/src/core/hle/service/apt/apt_a.h b/src/core/hle/service/apt/apt_a.h
index f481fa1c9..e17e2f323 100644
--- a/src/core/hle/service/apt/apt_a.h
+++ b/src/core/hle/service/apt/apt_a.h
@@ -11,6 +11,12 @@ namespace Service::APT {
 class APT_A final : public Module::APTInterface {
 public:
     explicit APT_A(std::shared_ptr<Module> apt);
+
+private:
+    SERVICE_SERIALIZATION(APT_A, apt, Module)
 };
 
 } // namespace Service::APT
+
+BOOST_CLASS_EXPORT_KEY(Service::APT::APT_A)
+BOOST_SERIALIZATION_CONSTRUCT(Service::APT::APT_A)
diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp
index 1843bdd9a..d0e5f5526 100644
--- a/src/core/hle/service/apt/apt_s.cpp
+++ b/src/core/hle/service/apt/apt_s.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/apt/apt_s.h"
 
 namespace Service::APT {
@@ -105,3 +106,5 @@ APT_S::APT_S(std::shared_ptr<Module> apt)
 }
 
 } // namespace Service::APT
+
+SERIALIZE_EXPORT_IMPL(Service::APT::APT_S)
diff --git a/src/core/hle/service/apt/apt_s.h b/src/core/hle/service/apt/apt_s.h
index 7e041cbda..d1dd27ff1 100644
--- a/src/core/hle/service/apt/apt_s.h
+++ b/src/core/hle/service/apt/apt_s.h
@@ -18,6 +18,12 @@ namespace Service::APT {
 class APT_S final : public Module::APTInterface {
 public:
     explicit APT_S(std::shared_ptr<Module> apt);
+
+private:
+    SERVICE_SERIALIZATION(APT_S, apt, Module)
 };
 
 } // namespace Service::APT
+
+BOOST_CLASS_EXPORT_KEY(Service::APT::APT_S)
+BOOST_SERIALIZATION_CONSTRUCT(Service::APT::APT_S)
diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp
index 0ecbd65da..05f531a83 100644
--- a/src/core/hle/service/apt/apt_u.cpp
+++ b/src/core/hle/service/apt/apt_u.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/apt/apt_u.h"
 
 namespace Service::APT {
@@ -102,3 +103,5 @@ APT_U::APT_U(std::shared_ptr<Module> apt)
 }
 
 } // namespace Service::APT
+
+SERIALIZE_EXPORT_IMPL(Service::APT::APT_U)
diff --git a/src/core/hle/service/apt/apt_u.h b/src/core/hle/service/apt/apt_u.h
index 3b342ed55..b8a8fe205 100644
--- a/src/core/hle/service/apt/apt_u.h
+++ b/src/core/hle/service/apt/apt_u.h
@@ -18,6 +18,12 @@ namespace Service::APT {
 class APT_U final : public Module::APTInterface {
 public:
     explicit APT_U(std::shared_ptr<Module> apt);
+
+private:
+    SERVICE_SERIALIZATION(APT_U, apt, Module)
 };
 
 } // namespace Service::APT
+
+BOOST_CLASS_EXPORT_KEY(Service::APT::APT_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::APT::APT_U)
diff --git a/src/core/hle/service/apt/ns_s.cpp b/src/core/hle/service/apt/ns_s.cpp
index e9eb87174..1f86b9a61 100644
--- a/src/core/hle/service/apt/ns_s.cpp
+++ b/src/core/hle/service/apt/ns_s.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/apt/ns_s.h"
 
 namespace Service::NS {
@@ -29,3 +30,5 @@ NS_S::NS_S(std::shared_ptr<Service::APT::Module> apt)
 }
 
 } // namespace Service::NS
+
+SERIALIZE_EXPORT_IMPL(Service::NS::NS_S)
diff --git a/src/core/hle/service/apt/ns_s.h b/src/core/hle/service/apt/ns_s.h
index 5a5b311b0..eaa0ad9d3 100644
--- a/src/core/hle/service/apt/ns_s.h
+++ b/src/core/hle/service/apt/ns_s.h
@@ -14,6 +14,12 @@ namespace Service::NS {
 class NS_S final : public Service::APT::Module::NSInterface {
 public:
     explicit NS_S(std::shared_ptr<Service::APT::Module> apt);
+
+private:
+    SERVICE_SERIALIZATION(NS_S, apt, Service::APT::Module)
 };
 
 } // namespace Service::NS
+
+BOOST_CLASS_EXPORT_KEY(Service::NS::NS_S)
+BOOST_SERIALIZATION_CONSTRUCT(Service::NS::NS_S)
diff --git a/src/core/hle/service/boss/boss.h b/src/core/hle/service/boss/boss.h
index e0cb39b37..e4000e851 100644
--- a/src/core/hle/service/boss/boss.h
+++ b/src/core/hle/service/boss/boss.h
@@ -5,6 +5,8 @@
 #pragma once
 
 #include <memory>
+#include <boost/serialization/shared_ptr.hpp>
+#include "core/global.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/service/service.h"
 
@@ -952,19 +954,42 @@ public:
          */
         void GetNsDataNewFlagPrivileged(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> boss;
 
+    private:
         u8 new_arrival_flag;
         u8 ns_data_new_flag;
         u8 ns_data_new_flag_privileged;
         u8 output_flag;
+
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& new_arrival_flag;
+            ar& ns_data_new_flag;
+            ar& ns_data_new_flag_privileged;
+            ar& output_flag;
+        }
+        friend class boost::serialization::access;
     };
 
 private:
     std::shared_ptr<Kernel::Event> task_finish_event;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& task_finish_event;
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::BOSS
+
+namespace boost::serialization {
+template <class Archive>
+void load_construct_data(Archive& ar, Service::BOSS::Module* t, const unsigned int) {
+    ::new (t) Service::BOSS::Module(Core::Global<Core::System>());
+}
+} // namespace boost::serialization
diff --git a/src/core/hle/service/boss/boss_p.cpp b/src/core/hle/service/boss/boss_p.cpp
index cdc8f1036..802f2643a 100644
--- a/src/core/hle/service/boss/boss_p.cpp
+++ b/src/core/hle/service/boss/boss_p.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/boss/boss_p.h"
 
 namespace Service::BOSS {
@@ -84,3 +85,5 @@ BOSS_P::BOSS_P(std::shared_ptr<Module> boss)
 }
 
 } // namespace Service::BOSS
+
+SERIALIZE_EXPORT_IMPL(Service::BOSS::BOSS_P)
diff --git a/src/core/hle/service/boss/boss_p.h b/src/core/hle/service/boss/boss_p.h
index 9c84a1e9d..56f0cd4fc 100644
--- a/src/core/hle/service/boss/boss_p.h
+++ b/src/core/hle/service/boss/boss_p.h
@@ -11,6 +11,12 @@ namespace Service::BOSS {
 class BOSS_P final : public Module::Interface {
 public:
     explicit BOSS_P(std::shared_ptr<Module> boss);
+
+private:
+    SERVICE_SERIALIZATION(BOSS_P, boss, Module)
 };
 
 } // namespace Service::BOSS
+
+BOOST_CLASS_EXPORT_KEY(Service::BOSS::BOSS_P)
+BOOST_SERIALIZATION_CONSTRUCT(Service::BOSS::BOSS_P)
diff --git a/src/core/hle/service/boss/boss_u.cpp b/src/core/hle/service/boss/boss_u.cpp
index c6aaba888..68a30510d 100644
--- a/src/core/hle/service/boss/boss_u.cpp
+++ b/src/core/hle/service/boss/boss_u.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/boss/boss_u.h"
 
 namespace Service::BOSS {
@@ -72,3 +73,5 @@ BOSS_U::BOSS_U(std::shared_ptr<Module> boss)
 }
 
 } // namespace Service::BOSS
+
+SERIALIZE_EXPORT_IMPL(Service::BOSS::BOSS_U)
diff --git a/src/core/hle/service/boss/boss_u.h b/src/core/hle/service/boss/boss_u.h
index a93b4e502..195783b40 100644
--- a/src/core/hle/service/boss/boss_u.h
+++ b/src/core/hle/service/boss/boss_u.h
@@ -11,6 +11,12 @@ namespace Service::BOSS {
 class BOSS_U final : public Module::Interface {
 public:
     explicit BOSS_U(std::shared_ptr<Module> boss);
+
+private:
+    SERVICE_SERIALIZATION(BOSS_U, boss, Module)
 };
 
 } // namespace Service::BOSS
+
+BOOST_CLASS_EXPORT_KEY(Service::BOSS::BOSS_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::BOSS::BOSS_U)
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index c5d24c314..c093ed7e5 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
+#include "common/archives.h"
 #include "common/bit_set.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -20,8 +21,32 @@
 #include "core/memory.h"
 #include "core/settings.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::CAM::Module)
+
 namespace Service::CAM {
 
+template <class Archive>
+void Module::serialize(Archive& ar, const unsigned int) {
+    ar& cameras;
+    ar& ports;
+    ar& is_camera_reload_pending;
+    if (Archive::is_loading::value) {
+        for (int i = 0; i < NumCameras; i++) {
+            LoadCameraImplementation(cameras[i], i);
+        }
+        for (std::size_t i = 0; i < ports.size(); i++) {
+            if (ports[i].is_busy) {
+                cameras[ports[i].camera_id].impl->StartCapture();
+            }
+            if (ports[i].is_receiving) {
+                StartReceiving(static_cast<int>(i));
+            }
+        }
+    }
+}
+
+SERIALIZE_IMPL(Module)
+
 // built-in resolution parameters
 constexpr std::array<Resolution, 8> PRESET_RESOLUTION{{
     {640, 480, 0, 0, 639, 479},  // VGA
diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h
index 989a3a6a7..7c36e02c5 100644
--- a/src/core/hle/service/cam/cam.h
+++ b/src/core/hle/service/cam/cam.h
@@ -9,8 +9,14 @@
 #include <future>
 #include <memory>
 #include <vector>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/deque.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unique_ptr.hpp>
+#include <boost/serialization/version.hpp>
 #include "common/common_types.h"
 #include "common/swap.h"
+#include "core/global.h"
 #include "core/hle/result.h"
 #include "core/hle/service/service.h"
 
@@ -180,6 +186,18 @@ struct Resolution {
     u16 crop_y0;
     u16 crop_x1;
     u16 crop_y1;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& width;
+        ar& height;
+        ar& crop_x0;
+        ar& crop_y0;
+        ar& crop_x1;
+        ar& crop_y1;
+    }
+    friend class boost::serialization::access;
 };
 
 struct PackageParameterWithoutContext {
@@ -726,7 +744,7 @@ public:
          */
         void DriverFinalize(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> cam;
     };
 
@@ -755,6 +773,16 @@ private:
         Effect effect{Effect::None};
         OutputFormat format{OutputFormat::YUV422};
         Resolution resolution = {0, 0, 0, 0, 0, 0};
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& flip;
+            ar& effect;
+            ar& format;
+            ar& resolution;
+        }
+        friend class boost::serialization::access;
     };
 
     struct CameraConfig {
@@ -762,6 +790,20 @@ private:
         std::array<ContextConfig, 2> contexts;
         int current_context{0};
         FrameRate frame_rate{FrameRate::Rate_15};
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int file_version) {
+            // For compatibility: put a nullptr here
+            if (file_version == 0) {
+                std::unique_ptr<Camera::CameraInterface> x;
+                ar& x;
+            }
+            ar& contexts;
+            ar& current_context;
+            ar& frame_rate;
+        }
+        friend class boost::serialization::access;
     };
 
     struct PortConfig {
@@ -798,6 +840,31 @@ private:
         u32 dest_size{0}; // the destination size of the receiving process
 
         void Clear();
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& camera_id;
+            ar& is_active;
+            ar& is_pending_receiving;
+            ar& is_busy;
+            ar& is_receiving;
+            ar& is_trimming;
+            ar& x0;
+            ar& y0;
+            ar& x1;
+            ar& y1;
+            ar& transfer_bytes;
+            ar& completion_event;
+            ar& buffer_error_interrupt_event;
+            ar& vsync_interrupt_event;
+            ar& vsync_timings;
+            // Ignore capture_result. In-progress captures might be affected but this is OK.
+            ar& dest_process;
+            ar& dest;
+            ar& dest_size;
+        }
+        friend class boost::serialization::access;
     };
 
     void LoadCameraImplementation(CameraConfig& camera, int camera_id);
@@ -808,6 +875,10 @@ private:
     Core::TimingEventType* completion_event_callback;
     Core::TimingEventType* vsync_interrupt_event_callback;
     std::atomic<bool> is_camera_reload_pending{false};
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 std::shared_ptr<Module> GetModule(Core::System& system);
@@ -815,3 +886,6 @@ std::shared_ptr<Module> GetModule(Core::System& system);
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::CAM
+
+SERVICE_CONSTRUCT(Service::CAM::Module)
+BOOST_CLASS_VERSION(Service::CAM::Module::CameraConfig, 1)
diff --git a/src/core/hle/service/cam/cam_c.cpp b/src/core/hle/service/cam/cam_c.cpp
index e5aca5361..10d04168e 100644
--- a/src/core/hle/service/cam/cam_c.cpp
+++ b/src/core/hle/service/cam/cam_c.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cam/cam.h"
 #include "core/hle/service/cam/cam_c.h"
 
@@ -79,3 +80,5 @@ CAM_C::CAM_C(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
 }
 
 } // namespace Service::CAM
+
+SERIALIZE_EXPORT_IMPL(Service::CAM::CAM_C)
diff --git a/src/core/hle/service/cam/cam_c.h b/src/core/hle/service/cam/cam_c.h
index d6dfcd6c5..c1ed355ac 100644
--- a/src/core/hle/service/cam/cam_c.h
+++ b/src/core/hle/service/cam/cam_c.h
@@ -11,6 +11,12 @@ namespace Service::CAM {
 class CAM_C final : public Module::Interface {
 public:
     explicit CAM_C(std::shared_ptr<Module> cam);
+
+private:
+    SERVICE_SERIALIZATION(CAM_C, cam, Module)
 };
 
 } // namespace Service::CAM
+
+BOOST_CLASS_EXPORT_KEY(Service::CAM::CAM_C)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CAM::CAM_C)
diff --git a/src/core/hle/service/cam/cam_q.cpp b/src/core/hle/service/cam/cam_q.cpp
index 71fc127d2..6f0c04598 100644
--- a/src/core/hle/service/cam/cam_q.cpp
+++ b/src/core/hle/service/cam/cam_q.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cam/cam_q.h"
 
 namespace Service::CAM {
@@ -13,3 +14,5 @@ CAM_Q::CAM_Q() : ServiceFramework("cam:q", 1 /*TODO: find the true value*/) {
 }
 
 } // namespace Service::CAM
+
+SERIALIZE_EXPORT_IMPL(Service::CAM::CAM_Q)
diff --git a/src/core/hle/service/cam/cam_q.h b/src/core/hle/service/cam/cam_q.h
index d1124493b..992b38390 100644
--- a/src/core/hle/service/cam/cam_q.h
+++ b/src/core/hle/service/cam/cam_q.h
@@ -11,6 +11,11 @@ namespace Service::CAM {
 class CAM_Q : public ServiceFramework<CAM_Q> {
 public:
     CAM_Q();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::CAM
+
+BOOST_CLASS_EXPORT_KEY(Service::CAM::CAM_Q)
diff --git a/src/core/hle/service/cam/cam_s.cpp b/src/core/hle/service/cam/cam_s.cpp
index 606c8d3af..771f0714c 100644
--- a/src/core/hle/service/cam/cam_s.cpp
+++ b/src/core/hle/service/cam/cam_s.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cam/cam.h"
 #include "core/hle/service/cam/cam_s.h"
 
@@ -79,3 +80,5 @@ CAM_S::CAM_S(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
 }
 
 } // namespace Service::CAM
+
+SERIALIZE_EXPORT_IMPL(Service::CAM::CAM_S)
diff --git a/src/core/hle/service/cam/cam_s.h b/src/core/hle/service/cam/cam_s.h
index 0c9d26644..cceb99b87 100644
--- a/src/core/hle/service/cam/cam_s.h
+++ b/src/core/hle/service/cam/cam_s.h
@@ -11,6 +11,12 @@ namespace Service::CAM {
 class CAM_S final : public Module::Interface {
 public:
     explicit CAM_S(std::shared_ptr<Module> cam);
+
+private:
+    SERVICE_SERIALIZATION(CAM_S, cam, Module)
 };
 
 } // namespace Service::CAM
+
+BOOST_CLASS_EXPORT_KEY(Service::CAM::CAM_S)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CAM::CAM_S)
diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp
index 83cace5a7..30e64e0f8 100644
--- a/src/core/hle/service/cam/cam_u.cpp
+++ b/src/core/hle/service/cam/cam_u.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cam/cam.h"
 #include "core/hle/service/cam/cam_u.h"
 
@@ -79,3 +80,5 @@ CAM_U::CAM_U(std::shared_ptr<Module> cam) : Module::Interface(std::move(cam), "c
 }
 
 } // namespace Service::CAM
+
+SERIALIZE_EXPORT_IMPL(Service::CAM::CAM_U)
diff --git a/src/core/hle/service/cam/cam_u.h b/src/core/hle/service/cam/cam_u.h
index 85b12559a..2b775a035 100644
--- a/src/core/hle/service/cam/cam_u.h
+++ b/src/core/hle/service/cam/cam_u.h
@@ -11,6 +11,12 @@ namespace Service::CAM {
 class CAM_U final : public Module::Interface {
 public:
     explicit CAM_U(std::shared_ptr<Module> cam);
+
+private:
+    SERVICE_SERIALIZATION(CAM_U, cam, Module)
 };
 
 } // namespace Service::CAM
+
+BOOST_CLASS_EXPORT_KEY(Service::CAM::CAM_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CAM::CAM_U)
diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp
index 281f48026..977a47842 100644
--- a/src/core/hle/service/cecd/cecd.cpp
+++ b/src/core/hle/service/cecd/cecd.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unique_ptr.hpp>
 #include <cryptopp/base64.h>
 #include <cryptopp/hmac.h>
 #include <cryptopp/sha.h>
+#include "common/archives.h"
 #include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -24,8 +27,20 @@
 #include "core/hle/service/cfg/cfg.h"
 #include "fmt/format.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::CECD::Module)
+SERIALIZE_EXPORT_IMPL(Service::CECD::Module)
+SERIALIZE_EXPORT_IMPL(Service::CECD::Module::SessionData)
+
 namespace Service::CECD {
 
+template <class Archive>
+void Module::serialize(Archive& ar, const unsigned int) {
+    ar& cecd_system_save_data_archive;
+    ar& cecinfo_event;
+    ar& change_state_event;
+}
+SERIALIZE_IMPL(Module)
+
 using CecDataPathType = Module::CecDataPathType;
 using CecOpenMode = Module::CecOpenMode;
 using CecSystemInfoType = Module::CecSystemInfoType;
@@ -93,7 +108,7 @@ void Module::Interface::Open(Kernel::HLERequestContext& ctx) {
         } else {
             session_data->file = std::move(file_result).Unwrap();
             rb.Push(RESULT_SUCCESS);
-            rb.Push<u32>(session_data->file->GetSize()); // Return file size
+            rb.Push<u32>(static_cast<u32>(session_data->file->GetSize())); // Return file size
         }
 
         if (path_type == CecDataPathType::MboxProgramId) {
@@ -141,8 +156,8 @@ void Module::Interface::Read(Kernel::HLERequestContext& ctx) {
         break;
     default: // If not directory, then it is a file
         std::vector<u8> buffer(write_buffer_size);
-        const u32 bytes_read =
-            session_data->file->Read(0, write_buffer_size, buffer.data()).Unwrap();
+        const u32 bytes_read = static_cast<u32>(
+            session_data->file->Read(0, write_buffer_size, buffer.data()).Unwrap());
 
         write_buffer.Write(buffer.data(), 0, write_buffer_size);
         session_data->file->Close();
@@ -184,7 +199,8 @@ void Module::Interface::ReadMessage(Kernel::HLERequestContext& ctx) {
         auto message = std::move(message_result).Unwrap();
         std::vector<u8> buffer(buffer_size);
 
-        const u32 bytes_read = message->Read(0, buffer_size, buffer.data()).Unwrap();
+        const u32 bytes_read =
+            static_cast<u32>(message->Read(0, buffer_size, buffer.data()).Unwrap());
         write_buffer.Write(buffer.data(), 0, buffer_size);
         message->Close();
 
@@ -253,7 +269,8 @@ void Module::Interface::ReadMessageWithHMAC(Kernel::HLERequestContext& ctx) {
         auto message = std::move(message_result).Unwrap();
         std::vector<u8> buffer(buffer_size);
 
-        const u32 bytes_read = message->Read(0, buffer_size, buffer.data()).Unwrap();
+        const u32 bytes_read =
+            static_cast<u32>(message->Read(0, buffer_size, buffer.data()).Unwrap());
         write_buffer.Write(buffer.data(), 0, buffer_size);
         message->Close();
 
@@ -354,8 +371,8 @@ void Module::Interface::Write(Kernel::HLERequestContext& ctx) {
                                      buffer);
         }
 
-        const u32 bytes_written =
-            session_data->file->Write(0, buffer.size(), true, buffer.data()).Unwrap();
+        const u32 bytes_written = static_cast<u32>(
+            session_data->file->Write(0, buffer.size(), true, buffer.data()).Unwrap());
         session_data->file->Close();
 
         rb.Push(RESULT_SUCCESS);
@@ -416,7 +433,8 @@ void Module::Interface::WriteMessage(Kernel::HLERequestContext& ctx) {
                   msg_header.sender_id, msg_header.sender_id2, msg_header.send_count,
                   msg_header.forward_count, msg_header.user_data);
 
-        const u32 bytes_written = message->Write(0, buffer_size, true, buffer.data()).Unwrap();
+        const u32 bytes_written =
+            static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap());
         message->Close();
 
         rb.Push(RESULT_SUCCESS);
@@ -502,7 +520,8 @@ void Module::Interface::WriteMessageWithHMAC(Kernel::HLERequestContext& ctx) {
         hmac.CalculateDigest(hmac_digest.data(), message_body.data(), msg_header.body_size);
         std::memcpy(buffer.data() + hmac_offset, hmac_digest.data(), hmac_size);
 
-        const u32 bytes_written = message->Write(0, buffer_size, true, buffer.data()).Unwrap();
+        const u32 bytes_written =
+            static_cast<u32>(message->Write(0, buffer_size, true, buffer.data()).Unwrap());
         message->Close();
 
         rb.Push(RESULT_SUCCESS);
@@ -744,7 +763,8 @@ void Module::Interface::OpenAndWrite(Kernel::HLERequestContext& ctx) {
                 cecd->CheckAndUpdateFile(path_type, ncch_program_id, buffer);
             }
 
-            const u32 bytes_written = file->Write(0, buffer.size(), true, buffer.data()).Unwrap();
+            const u32 bytes_written =
+                static_cast<u32>(file->Write(0, buffer.size(), true, buffer.data()).Unwrap());
             file->Close();
 
             rb.Push(RESULT_SUCCESS);
@@ -793,7 +813,8 @@ void Module::Interface::OpenAndRead(Kernel::HLERequestContext& ctx) {
             auto file = std::move(file_result).Unwrap();
             std::vector<u8> buffer(buffer_size);
 
-            const u32 bytes_read = file->Read(0, buffer_size, buffer.data()).Unwrap();
+            const u32 bytes_read =
+                static_cast<u32>(file->Read(0, buffer_size, buffer.data()).Unwrap());
             write_buffer.Write(buffer.data(), 0, buffer_size);
             file->Close();
 
@@ -924,7 +945,7 @@ void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_
     constexpr u32 max_num_boxes = 24;
     constexpr u32 name_size = 16;      // fixed size 16 characters long
     constexpr u32 valid_name_size = 8; // 8 characters are valid, the rest are null
-    const u32 file_size = file_buffer.size();
+    const u32 file_size = static_cast<u32>(file_buffer.size());
 
     switch (path_type) {
     case CecDataPathType::MboxList: {
@@ -1008,7 +1029,7 @@ void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_
                 std::u16string u16_filename;
 
                 // Loop through entries but don't add mboxlist____ to itself.
-                for (auto i = 0; i < entry_count; i++) {
+                for (u32 i = 0; i < entry_count; i++) {
                     u16_filename = std::u16string(entries[i].filename);
                     file_name = Common::UTF16ToUTF8(u16_filename);
 
@@ -1199,7 +1220,7 @@ void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_
         std::string file_name;
         std::u16string u16_filename;
 
-        for (auto i = 0; i < entry_count; i++) {
+        for (u32 i = 0; i < entry_count; i++) {
             u16_filename = std::u16string(entries[i].filename);
             file_name = Common::UTF16ToUTF8(u16_filename);
 
@@ -1217,7 +1238,7 @@ void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_
                 auto message_result = cecd_system_save_data_archive->OpenFile(message_path, mode);
 
                 auto message = std::move(message_result).Unwrap();
-                const u32 message_size = message->GetSize();
+                const u32 message_size = static_cast<u32>(message->GetSize());
                 std::vector<u8> buffer(message_size);
 
                 message->Read(0, message_size, buffer.data()).Unwrap();
@@ -1291,7 +1312,7 @@ void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_
         std::string file_name;
         std::u16string u16_filename;
 
-        for (auto i = 0; i < entry_count; i++) {
+        for (u32 i = 0; i < entry_count; i++) {
             u16_filename = std::u16string(entries[i].filename);
             file_name = Common::UTF16ToUTF8(u16_filename);
 
@@ -1307,7 +1328,7 @@ void Module::CheckAndUpdateFile(const CecDataPathType path_type, const u32 ncch_
                 auto message_result = cecd_system_save_data_archive->OpenFile(message_path, mode);
 
                 auto message = std::move(message_result).Unwrap();
-                const u32 message_size = message->GetSize();
+                const u32 message_size = static_cast<u32>(message->GetSize());
                 std::vector<u8> buffer(message_size);
 
                 message->Read(0, message_size, buffer.data()).Unwrap();
diff --git a/src/core/hle/service/cecd/cecd.h b/src/core/hle/service/cecd/cecd.h
index 004ef33e7..0870f31bf 100644
--- a/src/core/hle/service/cecd/cecd.h
+++ b/src/core/hle/service/cecd/cecd.h
@@ -248,6 +248,19 @@ public:
         FileSys::Path path;
 
         std::unique_ptr<FileSys::FileBackend> file;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& boost::serialization::base_object<Kernel::SessionRequestHandler::SessionDataBase>(
+                *this);
+            ar& ncch_program_id;
+            ar& data_path_type;
+            ar& open_mode.raw;
+            ar& path;
+            ar& file;
+        }
+        friend class boost::serialization::access;
     };
 
     class Interface : public ServiceFramework<Interface, SessionData> {
@@ -584,7 +597,7 @@ public:
          */
         void GetCecInfoEventHandleSys(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> cecd;
     };
 
@@ -613,9 +626,17 @@ private:
     std::shared_ptr<Kernel::Event> change_state_event;
 
     Core::System& system;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 /// Initialize CECD service(s)
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::CECD
+
+SERVICE_CONSTRUCT(Service::CECD::Module)
+BOOST_CLASS_EXPORT_KEY(Service::CECD::Module)
+BOOST_CLASS_EXPORT_KEY(Service::CECD::Module::SessionData)
diff --git a/src/core/hle/service/cecd/cecd_ndm.cpp b/src/core/hle/service/cecd/cecd_ndm.cpp
index e4366e9c6..4b571283d 100644
--- a/src/core/hle/service/cecd/cecd_ndm.cpp
+++ b/src/core/hle/service/cecd/cecd_ndm.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cecd/cecd_ndm.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CECD::CECD_NDM)
+
 namespace Service::CECD {
 
 CECD_NDM::CECD_NDM(std::shared_ptr<Module> cecd)
diff --git a/src/core/hle/service/cecd/cecd_ndm.h b/src/core/hle/service/cecd/cecd_ndm.h
index a9fabb1a1..9fd282585 100644
--- a/src/core/hle/service/cecd/cecd_ndm.h
+++ b/src/core/hle/service/cecd/cecd_ndm.h
@@ -11,6 +11,12 @@ namespace Service::CECD {
 class CECD_NDM final : public Module::Interface {
 public:
     explicit CECD_NDM(std::shared_ptr<Module> cecd);
+
+private:
+    SERVICE_SERIALIZATION(CECD_NDM, cecd, Module)
 };
 
 } // namespace Service::CECD
+
+BOOST_CLASS_EXPORT_KEY(Service::CECD::CECD_NDM)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CECD::CECD_NDM)
diff --git a/src/core/hle/service/cecd/cecd_s.cpp b/src/core/hle/service/cecd/cecd_s.cpp
index 3395c405d..fa838d2af 100644
--- a/src/core/hle/service/cecd/cecd_s.cpp
+++ b/src/core/hle/service/cecd/cecd_s.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cecd/cecd_s.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CECD::CECD_S)
+
 namespace Service::CECD {
 
 CECD_S::CECD_S(std::shared_ptr<Module> cecd)
diff --git a/src/core/hle/service/cecd/cecd_s.h b/src/core/hle/service/cecd/cecd_s.h
index 6c50b13ba..9c6a7afec 100644
--- a/src/core/hle/service/cecd/cecd_s.h
+++ b/src/core/hle/service/cecd/cecd_s.h
@@ -11,6 +11,12 @@ namespace Service::CECD {
 class CECD_S final : public Module::Interface {
 public:
     explicit CECD_S(std::shared_ptr<Module> cecd);
+
+private:
+    SERVICE_SERIALIZATION(CECD_S, cecd, Module)
 };
 
 } // namespace Service::CECD
+
+BOOST_CLASS_EXPORT_KEY(Service::CECD::CECD_S)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CECD::CECD_S)
diff --git a/src/core/hle/service/cecd/cecd_u.cpp b/src/core/hle/service/cecd/cecd_u.cpp
index 81fcd1019..e8cd7d1c9 100644
--- a/src/core/hle/service/cecd/cecd_u.cpp
+++ b/src/core/hle/service/cecd/cecd_u.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cecd/cecd_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CECD::CECD_U)
+
 namespace Service::CECD {
 
 CECD_U::CECD_U(std::shared_ptr<Module> cecd)
diff --git a/src/core/hle/service/cecd/cecd_u.h b/src/core/hle/service/cecd/cecd_u.h
index 49ddadb69..31e7a2367 100644
--- a/src/core/hle/service/cecd/cecd_u.h
+++ b/src/core/hle/service/cecd/cecd_u.h
@@ -11,6 +11,12 @@ namespace Service::CECD {
 class CECD_U final : public Module::Interface {
 public:
     explicit CECD_U(std::shared_ptr<Module> cecd);
+
+private:
+    SERVICE_SERIALIZATION(CECD_U, cecd, Module)
 };
 
 } // namespace Service::CECD
+
+BOOST_CLASS_EXPORT_KEY(Service::CECD::CECD_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CECD::CECD_U)
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index b9e9a6635..fc285f662 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -4,8 +4,11 @@
 
 #include <algorithm>
 #include <tuple>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/unique_ptr.hpp>
 #include <cryptopp/osrng.h>
 #include <cryptopp/sha.h>
+#include "common/archives.h"
 #include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -24,8 +27,18 @@
 #include "core/hle/service/cfg/cfg_u.h"
 #include "core/settings.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CFG::Module)
+
 namespace Service::CFG {
 
+template <class Archive>
+void Module::serialize(Archive& ar, const unsigned int) {
+    ar& cfg_config_file_buffer;
+    ar& cfg_system_save_data_archive;
+    ar& preferred_region_code;
+}
+SERIALIZE_IMPL(Module)
+
 /// The maximum number of block entries that can exist in the config file
 static const u32 CONFIG_FILE_MAX_BLOCK_ENTRIES = 1479;
 
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h
index 6886d513b..58e914583 100644
--- a/src/core/hle/service/cfg/cfg.h
+++ b/src/core/hle/service/cfg/cfg.h
@@ -244,7 +244,7 @@ public:
             (this->*function)(ctx, id);
         }
 
-    private:
+    protected:
         std::shared_ptr<Module> cfg;
     };
 
@@ -426,6 +426,10 @@ private:
     std::array<u8, CONFIG_SAVEFILE_SIZE> cfg_config_file_buffer;
     std::unique_ptr<FileSys::ArchiveBackend> cfg_system_save_data_archive;
     u32 preferred_region_code = 0;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 std::shared_ptr<Module> GetModule(Core::System& system);
@@ -436,3 +440,5 @@ void InstallInterfaces(Core::System& system);
 std::string GetConsoleIdHash(Core::System& system);
 
 } // namespace Service::CFG
+
+BOOST_CLASS_EXPORT_KEY(Service::CFG::Module)
diff --git a/src/core/hle/service/cfg/cfg_i.cpp b/src/core/hle/service/cfg/cfg_i.cpp
index 624299076..4d3d298a2 100644
--- a/src/core/hle/service/cfg/cfg_i.cpp
+++ b/src/core/hle/service/cfg/cfg_i.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cfg/cfg_i.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CFG::CFG_I)
+
 namespace Service::CFG {
 
 CFG_I::CFG_I(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "cfg:i", 23) {
diff --git a/src/core/hle/service/cfg/cfg_i.h b/src/core/hle/service/cfg/cfg_i.h
index 704eb4a71..ac8ffb990 100644
--- a/src/core/hle/service/cfg/cfg_i.h
+++ b/src/core/hle/service/cfg/cfg_i.h
@@ -11,6 +11,12 @@ namespace Service::CFG {
 class CFG_I final : public Module::Interface {
 public:
     explicit CFG_I(std::shared_ptr<Module> cfg);
+
+private:
+    SERVICE_SERIALIZATION(CFG_I, cfg, Module)
 };
 
 } // namespace Service::CFG
+
+BOOST_CLASS_EXPORT_KEY(Service::CFG::CFG_I)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CFG::CFG_I)
diff --git a/src/core/hle/service/cfg/cfg_nor.cpp b/src/core/hle/service/cfg/cfg_nor.cpp
index 413548313..0dd21076d 100644
--- a/src/core/hle/service/cfg/cfg_nor.cpp
+++ b/src/core/hle/service/cfg/cfg_nor.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cfg/cfg_nor.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CFG::CFG_NOR)
+
 namespace Service::CFG {
 
 CFG_NOR::CFG_NOR() : ServiceFramework("cfg:nor", 23) {
diff --git a/src/core/hle/service/cfg/cfg_nor.h b/src/core/hle/service/cfg/cfg_nor.h
index 3dace92bd..7e0a1a2b8 100644
--- a/src/core/hle/service/cfg/cfg_nor.h
+++ b/src/core/hle/service/cfg/cfg_nor.h
@@ -11,6 +11,11 @@ namespace Service::CFG {
 class CFG_NOR final : public ServiceFramework<CFG_NOR> {
 public:
     CFG_NOR();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::CFG
+
+BOOST_CLASS_EXPORT_KEY(Service::CFG::CFG_NOR)
diff --git a/src/core/hle/service/cfg/cfg_s.cpp b/src/core/hle/service/cfg/cfg_s.cpp
index a211dae05..773a412f7 100644
--- a/src/core/hle/service/cfg/cfg_s.cpp
+++ b/src/core/hle/service/cfg/cfg_s.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cfg/cfg_s.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CFG::CFG_S)
+
 namespace Service::CFG {
 
 CFG_S::CFG_S(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "cfg:s", 23) {
diff --git a/src/core/hle/service/cfg/cfg_s.h b/src/core/hle/service/cfg/cfg_s.h
index 7f135b357..d0cbc7a18 100644
--- a/src/core/hle/service/cfg/cfg_s.h
+++ b/src/core/hle/service/cfg/cfg_s.h
@@ -11,6 +11,12 @@ namespace Service::CFG {
 class CFG_S final : public Module::Interface {
 public:
     explicit CFG_S(std::shared_ptr<Module> cfg);
+
+private:
+    SERVICE_SERIALIZATION(CFG_S, cfg, Module)
 };
 
 } // namespace Service::CFG
+
+BOOST_CLASS_EXPORT_KEY(Service::CFG::CFG_S)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CFG::CFG_S)
diff --git a/src/core/hle/service/cfg/cfg_u.cpp b/src/core/hle/service/cfg/cfg_u.cpp
index 6aa862f82..bcc590bf8 100644
--- a/src/core/hle/service/cfg/cfg_u.cpp
+++ b/src/core/hle/service/cfg/cfg_u.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/cfg/cfg_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::CFG::CFG_U)
+
 namespace Service::CFG {
 
 CFG_U::CFG_U(std::shared_ptr<Module> cfg) : Module::Interface(std::move(cfg), "cfg:u", 23) {
diff --git a/src/core/hle/service/cfg/cfg_u.h b/src/core/hle/service/cfg/cfg_u.h
index 8b48e963e..906377124 100644
--- a/src/core/hle/service/cfg/cfg_u.h
+++ b/src/core/hle/service/cfg/cfg_u.h
@@ -11,6 +11,12 @@ namespace Service::CFG {
 class CFG_U final : public Module::Interface {
 public:
     explicit CFG_U(std::shared_ptr<Module> cfg);
+
+private:
+    SERVICE_SERIALIZATION(CFG_U, cfg, Module)
 };
 
 } // namespace Service::CFG
+
+BOOST_CLASS_EXPORT_KEY(Service::CFG::CFG_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::CFG::CFG_U)
diff --git a/src/core/hle/service/csnd/csnd_snd.cpp b/src/core/hle/service/csnd/csnd_snd.cpp
index 5c9fa13e9..1a7a00964 100644
--- a/src/core/hle/service/csnd/csnd_snd.cpp
+++ b/src/core/hle/service/csnd/csnd_snd.cpp
@@ -3,11 +3,15 @@
 // Refer to the license.txt file included.
 
 #include "common/alignment.h"
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/result.h"
 #include "core/hle/service/csnd/csnd_snd.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::CSND::CSND_SND)
+SERIALIZE_EXPORT_IMPL(Service::CSND::CSND_SND)
+
 namespace Service::CSND {
 
 enum class CommandId : u16 {
diff --git a/src/core/hle/service/csnd/csnd_snd.h b/src/core/hle/service/csnd/csnd_snd.h
index afdc3b1a8..0b4cd4331 100644
--- a/src/core/hle/service/csnd/csnd_snd.h
+++ b/src/core/hle/service/csnd/csnd_snd.h
@@ -5,6 +5,8 @@
 #pragma once
 
 #include <memory>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "core/hle/kernel/mutex.h"
 #include "core/hle/kernel/shared_memory.h"
 #include "core/hle/service/service.h"
@@ -33,6 +35,14 @@ enum class LoopMode : u8 {
 struct AdpcmState {
     s16 predictor = 0;
     u8 step_index = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& predictor;
+        ar& step_index;
+    }
+    friend class boost::serialization::access;
 };
 
 struct Channel {
@@ -52,6 +62,28 @@ struct Channel {
     LoopMode loop_mode = LoopMode::Manual;
     Encoding encoding = Encoding::Pcm8;
     u8 psg_duty = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& block1_address;
+        ar& block2_address;
+        ar& block1_size;
+        ar& block2_size;
+        ar& block1_adpcm_state;
+        ar& block2_adpcm_state;
+        ar& block2_adpcm_reload;
+        ar& left_channel_volume;
+        ar& right_channel_volume;
+        ar& left_capture_volume;
+        ar& right_capture_volume;
+        ar& sample_rate;
+        ar& linear_interpolation;
+        ar& loop_mode;
+        ar& encoding;
+        ar& psg_duty;
+    }
+    friend class boost::serialization::access;
 };
 
 class CSND_SND final : public ServiceFramework<CSND_SND> {
@@ -222,9 +254,27 @@ private:
     u32 type1_command_offset = 0;
 
     u32 acquired_channel_mask = 0;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& mutex;
+        ar& shared_memory;
+        ar& capture_units;
+        ar& channels;
+        ar& master_state_offset;
+        ar& channel_state_offset;
+        ar& capture_state_offset;
+        ar& type1_command_offset;
+        ar& acquired_channel_mask;
+    }
+    friend class boost::serialization::access;
 };
 
 /// Initializes the CSND_SND Service
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::CSND
+
+BOOST_CLASS_EXPORT_KEY(Service::CSND::CSND_SND)
+SERVICE_CONSTRUCT(Service::CSND::CSND_SND)
diff --git a/src/core/hle/service/dlp/dlp_clnt.cpp b/src/core/hle/service/dlp/dlp_clnt.cpp
index 63308f57e..f8b315c00 100644
--- a/src/core/hle/service/dlp/dlp_clnt.cpp
+++ b/src/core/hle/service/dlp/dlp_clnt.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/dlp/dlp_clnt.h"
 
+SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_CLNT)
+
 namespace Service::DLP {
 
 DLP_CLNT::DLP_CLNT() : ServiceFramework("dlp:CLNT", 1) {
diff --git a/src/core/hle/service/dlp/dlp_clnt.h b/src/core/hle/service/dlp/dlp_clnt.h
index db506b985..ac6933e7e 100644
--- a/src/core/hle/service/dlp/dlp_clnt.h
+++ b/src/core/hle/service/dlp/dlp_clnt.h
@@ -12,6 +12,11 @@ class DLP_CLNT final : public ServiceFramework<DLP_CLNT> {
 public:
     DLP_CLNT();
     ~DLP_CLNT() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::DLP
+
+BOOST_CLASS_EXPORT_KEY(Service::DLP::DLP_CLNT)
diff --git a/src/core/hle/service/dlp/dlp_fkcl.cpp b/src/core/hle/service/dlp/dlp_fkcl.cpp
index 30a98c4bf..948b25b67 100644
--- a/src/core/hle/service/dlp/dlp_fkcl.cpp
+++ b/src/core/hle/service/dlp/dlp_fkcl.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/dlp/dlp_fkcl.h"
 
+SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_FKCL)
+
 namespace Service::DLP {
 
 DLP_FKCL::DLP_FKCL() : ServiceFramework("dlp:FKCL", 1) {
diff --git a/src/core/hle/service/dlp/dlp_fkcl.h b/src/core/hle/service/dlp/dlp_fkcl.h
index a3b2ac86d..c05a77b49 100644
--- a/src/core/hle/service/dlp/dlp_fkcl.h
+++ b/src/core/hle/service/dlp/dlp_fkcl.h
@@ -12,6 +12,11 @@ class DLP_FKCL final : public ServiceFramework<DLP_FKCL> {
 public:
     DLP_FKCL();
     ~DLP_FKCL() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::DLP
+
+BOOST_CLASS_EXPORT_KEY(Service::DLP::DLP_FKCL)
diff --git a/src/core/hle/service/dlp/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp
index 9d7405941..87733b71f 100644
--- a/src/core/hle/service/dlp/dlp_srvr.cpp
+++ b/src/core/hle/service/dlp/dlp_srvr.cpp
@@ -2,12 +2,15 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/result.h"
 #include "core/hle/service/dlp/dlp_srvr.h"
 
+SERIALIZE_EXPORT_IMPL(Service::DLP::DLP_SRVR)
+
 namespace Service::DLP {
 
 void DLP_SRVR::IsChild(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/dlp/dlp_srvr.h b/src/core/hle/service/dlp/dlp_srvr.h
index 50d8d92b2..625740d2f 100644
--- a/src/core/hle/service/dlp/dlp_srvr.h
+++ b/src/core/hle/service/dlp/dlp_srvr.h
@@ -15,6 +15,10 @@ public:
 
 private:
     void IsChild(Kernel::HLERequestContext& ctx);
+
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::DLP
+
+BOOST_CLASS_EXPORT_KEY(Service::DLP::DLP_SRVR)
diff --git a/src/core/hle/service/dsp/dsp_dsp.cpp b/src/core/hle/service/dsp/dsp_dsp.cpp
index 37682edcc..9b0ef8986 100644
--- a/src/core/hle/service/dsp/dsp_dsp.cpp
+++ b/src/core/hle/service/dsp/dsp_dsp.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include "audio_core/audio_types.h"
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -13,6 +14,9 @@
 using DspPipe = AudioCore::DspPipe;
 using InterruptType = Service::DSP::DSP_DSP::InterruptType;
 
+SERIALIZE_EXPORT_IMPL(Service::DSP::DSP_DSP)
+SERVICE_CONSTRUCT_IMPL(Service::DSP::DSP_DSP)
+
 namespace AudioCore {
 enum class DspPipe;
 }
diff --git a/src/core/hle/service/dsp/dsp_dsp.h b/src/core/hle/service/dsp/dsp_dsp.h
index ef1f0b76d..d580b3d00 100644
--- a/src/core/hle/service/dsp/dsp_dsp.h
+++ b/src/core/hle/service/dsp/dsp_dsp.h
@@ -5,6 +5,9 @@
 #pragma once
 
 #include <memory>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "audio_core/dsp_interface.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/result.h"
@@ -264,8 +267,22 @@ private:
 
     /// Each DSP pipe has an associated interrupt
     std::array<std::shared_ptr<Kernel::Event>, AudioCore::num_dsp_pipe> pipes = {{}};
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& semaphore_event;
+        ar& preset_semaphore;
+        ar& interrupt_zero;
+        ar& interrupt_one;
+        ar& pipes;
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::DSP
+
+BOOST_CLASS_EXPORT_KEY(Service::DSP::DSP_DSP)
+SERVICE_CONSTRUCT(Service::DSP::DSP_DSP)
diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp
index 13b2c7e46..85759f9d2 100644
--- a/src/core/hle/service/err_f.cpp
+++ b/src/core/hle/service/err_f.cpp
@@ -6,6 +6,7 @@
 #include <chrono>
 #include <iomanip>
 #include <sstream>
+#include "common/archives.h"
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
@@ -14,6 +15,20 @@
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/result.h"
 #include "core/hle/service/err_f.h"
+#undef exception_info // We use 'exception_info' as a plain identifier, but MSVC defines this in one
+                      // of its many headers.
+
+SERIALIZE_EXPORT_IMPL(Service::ERR::ERR_F)
+
+namespace boost::serialization {
+template <class Archive>
+void load_construct_data(Archive& ar, Service::ERR::ERR_F* t, const unsigned int) {
+    ::new (t) Service::ERR::ERR_F(Core::Global<Core::System>());
+}
+
+template void load_construct_data<iarchive>(iarchive& ar, Service::ERR::ERR_F* t,
+                                            const unsigned int);
+} // namespace boost::serialization
 
 namespace Service::ERR {
 
diff --git a/src/core/hle/service/err_f.h b/src/core/hle/service/err_f.h
index 4a1684caf..1b9fad452 100644
--- a/src/core/hle/service/err_f.h
+++ b/src/core/hle/service/err_f.h
@@ -34,8 +34,17 @@ private:
     void ThrowFatalError(Kernel::HLERequestContext& ctx);
 
     Core::System& system;
+
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::ERR
+
+BOOST_CLASS_EXPORT_KEY(Service::ERR::ERR_F)
+
+namespace boost::serialization {
+template <class Archive>
+void load_construct_data(Archive& ar, Service::ERR::ERR_F* t, const unsigned int);
+}
diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h
index 200b51ebe..1f34e61fa 100644
--- a/src/core/hle/service/frd/frd.h
+++ b/src/core/hle/service/frd/frd.h
@@ -18,10 +18,26 @@ struct FriendKey {
     u32 friend_id;
     u32 unknown;
     u64 friend_code;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& friend_id;
+        ar& unknown;
+        ar& friend_code;
+    }
+    friend class boost::serialization::access;
 };
 
 struct MyPresence {
     u8 unknown[0x12C];
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& unknown;
+    }
+    friend class boost::serialization::access;
 };
 
 struct Profile {
@@ -130,13 +146,20 @@ public:
          */
         void SetClientSdkVersion(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> frd;
     };
 
 private:
     FriendKey my_friend_key = {0, 0, 0ull};
     MyPresence my_presence = {};
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& my_friend_key;
+        ar& my_presence;
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
diff --git a/src/core/hle/service/frd/frd_a.cpp b/src/core/hle/service/frd/frd_a.cpp
index c68689cc0..23a83a55e 100644
--- a/src/core/hle/service/frd/frd_a.cpp
+++ b/src/core/hle/service/frd/frd_a.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/frd/frd_a.h"
 
+SERIALIZE_EXPORT_IMPL(Service::FRD::FRD_A)
+
 namespace Service::FRD {
 
 FRD_A::FRD_A(std::shared_ptr<Module> frd) : Module::Interface(std::move(frd), "frd:a", 8) {
diff --git a/src/core/hle/service/frd/frd_a.h b/src/core/hle/service/frd/frd_a.h
index 97657a072..0bdd87525 100644
--- a/src/core/hle/service/frd/frd_a.h
+++ b/src/core/hle/service/frd/frd_a.h
@@ -11,6 +11,12 @@ namespace Service::FRD {
 class FRD_A final : public Module::Interface {
 public:
     explicit FRD_A(std::shared_ptr<Module> frd);
+
+private:
+    SERVICE_SERIALIZATION(FRD_A, frd, Module)
 };
 
 } // namespace Service::FRD
+
+BOOST_CLASS_EXPORT_KEY(Service::FRD::FRD_A)
+BOOST_SERIALIZATION_CONSTRUCT(Service::FRD::FRD_A)
diff --git a/src/core/hle/service/frd/frd_u.cpp b/src/core/hle/service/frd/frd_u.cpp
index 2cbc64243..d83c8ac8c 100644
--- a/src/core/hle/service/frd/frd_u.cpp
+++ b/src/core/hle/service/frd/frd_u.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/frd/frd_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::FRD::FRD_U)
+
 namespace Service::FRD {
 
 FRD_U::FRD_U(std::shared_ptr<Module> frd) : Module::Interface(std::move(frd), "frd:u", 8) {
diff --git a/src/core/hle/service/frd/frd_u.h b/src/core/hle/service/frd/frd_u.h
index 5704d5e11..e6adc37fb 100644
--- a/src/core/hle/service/frd/frd_u.h
+++ b/src/core/hle/service/frd/frd_u.h
@@ -11,6 +11,12 @@ namespace Service::FRD {
 class FRD_U final : public Module::Interface {
 public:
     explicit FRD_U(std::shared_ptr<Module> frd);
+
+private:
+    SERVICE_SERIALIZATION(FRD_U, frd, Module)
 };
 
 } // namespace Service::FRD
+
+BOOST_CLASS_EXPORT_KEY(Service::FRD::FRD_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::FRD::FRD_U)
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 6e179978b..767faf167 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -13,6 +13,7 @@
 #include "common/common_types.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "core/core.h"
 #include "core/file_sys/archive_backend.h"
 #include "core/file_sys/archive_extsavedata.h"
 #include "core/file_sys/archive_ncch.h"
@@ -90,7 +91,7 @@ ArchiveManager::OpenFileFromArchive(ArchiveHandle archive_handle, const FileSys:
     if (backend.Failed())
         return std::make_tuple(backend.Code(), open_timeout_ns);
 
-    auto file = std::shared_ptr<File>(new File(system, std::move(backend).Unwrap(), path));
+    auto file = std::shared_ptr<File>(new File(system.Kernel(), std::move(backend).Unwrap(), path));
     return std::make_tuple(MakeResult<std::shared_ptr<File>>(std::move(file)), open_timeout_ns);
 }
 
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 862e74980..aba06ac6f 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -8,7 +8,8 @@
 #include <string>
 #include <unordered_map>
 #include <vector>
-#include <boost/container/flat_map.hpp>
+#include <boost/serialization/unique_ptr.hpp>
+#include <boost/serialization/unordered_map.hpp>
 #include "common/common_types.h"
 #include "core/file_sys/archive_backend.h"
 #include "core/hle/result.h"
@@ -253,13 +254,21 @@ private:
      * Map of registered archives, identified by id code. Once an archive is registered here, it is
      * never removed until UnregisterArchiveTypes is called.
      */
-    boost::container::flat_map<ArchiveIdCode, std::unique_ptr<ArchiveFactory>> id_code_map;
+    std::unordered_map<ArchiveIdCode, std::unique_ptr<ArchiveFactory>> id_code_map;
 
     /**
      * Map of active archive handles to archive objects
      */
     std::unordered_map<ArchiveHandle, std::unique_ptr<ArchiveBackend>> handle_map;
     ArchiveHandle next_handle = 1;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& id_code_map;
+        ar& handle_map;
+        ar& next_handle;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::FS
diff --git a/src/core/hle/service/fs/directory.cpp b/src/core/hle/service/fs/directory.cpp
index a72a9307a..655c5602e 100644
--- a/src/core/hle/service/fs/directory.cpp
+++ b/src/core/hle/service/fs/directory.cpp
@@ -2,16 +2,33 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/unique_ptr.hpp>
+#include "common/archives.h"
 #include "common/logging/log.h"
 #include "core/file_sys/directory_backend.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/fs/directory.h"
 
+SERIALIZE_EXPORT_IMPL(Service::FS::Directory)
+
 namespace Service::FS {
 
+template <class Archive>
+void Directory::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar& path;
+    ar& backend;
+}
+
 Directory::Directory(std::unique_ptr<FileSys::DirectoryBackend>&& backend,
                      const FileSys::Path& path)
-    : ServiceFramework("", 1), path(path), backend(std::move(backend)) {
+    : Directory() {
+    this->backend = std::move(backend);
+    this->path = path;
+}
+
+Directory::Directory() : ServiceFramework("", 1), path(""), backend(nullptr) {
     static const FunctionInfo functions[] = {
         // clang-format off
         {0x08010042, &Directory::Read, "Read"},
diff --git a/src/core/hle/service/fs/directory.h b/src/core/hle/service/fs/directory.h
index 890b26648..77956b166 100644
--- a/src/core/hle/service/fs/directory.h
+++ b/src/core/hle/service/fs/directory.h
@@ -25,6 +25,15 @@ public:
 protected:
     void Read(Kernel::HLERequestContext& ctx);
     void Close(Kernel::HLERequestContext& ctx);
+
+private:
+    Directory();
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::FS
+
+BOOST_CLASS_EXPORT_KEY(Service::FS::Directory)
diff --git a/src/core/hle/service/fs/file.cpp b/src/core/hle/service/fs/file.cpp
index f91cbee99..3d9eee594 100644
--- a/src/core/hle/service/fs/file.cpp
+++ b/src/core/hle/service/fs/file.cpp
@@ -2,6 +2,8 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/unique_ptr.hpp>
+#include "common/archives.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/file_sys/errors.h"
@@ -13,11 +15,29 @@
 #include "core/hle/kernel/server_session.h"
 #include "core/hle/service/fs/file.h"
 
+SERIALIZE_EXPORT_IMPL(Service::FS::File)
+SERIALIZE_EXPORT_IMPL(Service::FS::FileSessionSlot)
+
 namespace Service::FS {
 
-File::File(Core::System& system, std::unique_ptr<FileSys::FileBackend>&& backend,
+template <class Archive>
+void File::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar& path;
+    ar& backend;
+}
+
+File::File() : File(Core::Global<Kernel::KernelSystem>()) {}
+
+File::File(Kernel::KernelSystem& kernel, std::unique_ptr<FileSys::FileBackend>&& backend,
            const FileSys::Path& path)
-    : ServiceFramework("", 1), path(path), backend(std::move(backend)), system(system) {
+    : File(kernel) {
+    this->backend = std::move(backend);
+    this->path = path;
+}
+
+File::File(Kernel::KernelSystem& kernel)
+    : ServiceFramework("", 1), path(""), backend(nullptr), kernel(kernel) {
     static const FunctionInfo functions[] = {
         {0x08010100, &File::OpenSubFile, "OpenSubFile"},
         {0x080200C2, &File::Read, "Read"},
@@ -71,12 +91,7 @@ void File::Read(Kernel::HLERequestContext& ctx) {
     rb.PushMappedBuffer(buffer);
 
     std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)};
-    ctx.SleepClientThread("file::read", read_timeout_ns,
-                          [](std::shared_ptr<Kernel::Thread> /*thread*/,
-                             Kernel::HLERequestContext& /*ctx*/,
-                             Kernel::ThreadWakeupReason /*reason*/) {
-                              // Nothing to do here
-                          });
+    ctx.SleepClientThread("file::read", read_timeout_ns, nullptr);
 }
 
 void File::Write(Kernel::HLERequestContext& ctx) {
@@ -201,7 +216,7 @@ void File::OpenLinkFile(Kernel::HLERequestContext& ctx) {
     using Kernel::ServerSession;
     IPC::RequestParser rp(ctx, 0x080C, 0, 0);
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
-    auto [server, client] = system.Kernel().CreateSessionPair(GetName());
+    auto [server, client] = kernel.CreateSessionPair(GetName());
     ClientConnected(server);
 
     FileSessionSlot* slot = GetSessionData(server);
@@ -247,7 +262,7 @@ void File::OpenSubFile(Kernel::HLERequestContext& ctx) {
 
     using Kernel::ClientSession;
     using Kernel::ServerSession;
-    auto [server, client] = system.Kernel().CreateSessionPair(GetName());
+    auto [server, client] = kernel.CreateSessionPair(GetName());
     ClientConnected(server);
 
     FileSessionSlot* slot = GetSessionData(server);
@@ -261,7 +276,7 @@ void File::OpenSubFile(Kernel::HLERequestContext& ctx) {
 }
 
 std::shared_ptr<Kernel::ClientSession> File::Connect() {
-    auto [server, client] = system.Kernel().CreateSessionPair(GetName());
+    auto [server, client] = kernel.CreateSessionPair(GetName());
     ClientConnected(server);
 
     FileSessionSlot* slot = GetSessionData(server);
diff --git a/src/core/hle/service/fs/file.h b/src/core/hle/service/fs/file.h
index 062fcd5e7..a6ef69304 100644
--- a/src/core/hle/service/fs/file.h
+++ b/src/core/hle/service/fs/file.h
@@ -5,7 +5,9 @@
 #pragma once
 
 #include <memory>
+#include <boost/serialization/base_object.hpp>
 #include "core/file_sys/archive_backend.h"
+#include "core/global.h"
 #include "core/hle/service/service.h"
 
 namespace Core {
@@ -19,13 +21,25 @@ struct FileSessionSlot : public Kernel::SessionRequestHandler::SessionDataBase {
     u64 offset;   ///< Offset that this session will start reading from.
     u64 size;     ///< Max size of the file that this session is allowed to access
     bool subfile; ///< Whether this file was opened via OpenSubFile or not.
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler::SessionDataBase>(
+            *this);
+        ar& priority;
+        ar& offset;
+        ar& size;
+        ar& subfile;
+    }
+    friend class boost::serialization::access;
 };
 
 // TODO: File is not a real service, but it can still utilize ServiceFramework::RegisterHandlers.
 // Consider splitting ServiceFramework interface.
 class File final : public ServiceFramework<File, FileSessionSlot> {
 public:
-    File(Core::System& system, std::unique_ptr<FileSys::FileBackend>&& backend,
+    File(Kernel::KernelSystem& kernel, std::unique_ptr<FileSys::FileBackend>&& backend,
          const FileSys::Path& path);
     ~File() = default;
 
@@ -59,7 +73,17 @@ private:
     void OpenLinkFile(Kernel::HLERequestContext& ctx);
     void OpenSubFile(Kernel::HLERequestContext& ctx);
 
-    Core::System& system;
+    Kernel::KernelSystem& kernel;
+
+    File(Kernel::KernelSystem& kernel);
+    File();
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::FS
+
+BOOST_CLASS_EXPORT_KEY(Service::FS::FileSessionSlot)
+BOOST_CLASS_EXPORT_KEY(Service::FS::File)
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index bca8e77e5..010600ee2 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <cinttypes>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/file_util.h"
@@ -25,6 +26,10 @@
 #include "core/hle/service/fs/fs_user.h"
 #include "core/settings.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::FS::FS_USER)
+SERIALIZE_EXPORT_IMPL(Service::FS::FS_USER)
+SERIALIZE_EXPORT_IMPL(Service::FS::ClientSlot)
+
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 // Namespace FS_User
 
@@ -71,12 +76,7 @@ void FS_USER::OpenFile(Kernel::HLERequestContext& ctx) {
         LOG_ERROR(Service_FS, "failed to get a handle for file {}", file_path.DebugStr());
     }
 
-    ctx.SleepClientThread("fs_user::open", open_timeout_ns,
-                          [](std::shared_ptr<Kernel::Thread> /*thread*/,
-                             Kernel::HLERequestContext& /*ctx*/,
-                             Kernel::ThreadWakeupReason /*reason*/) {
-                              // Nothing to do here
-                          });
+    ctx.SleepClientThread("fs_user::open", open_timeout_ns, nullptr);
 }
 
 void FS_USER::OpenFileDirectly(Kernel::HLERequestContext& ctx) {
@@ -129,12 +129,7 @@ void FS_USER::OpenFileDirectly(Kernel::HLERequestContext& ctx) {
                   file_path.DebugStr(), mode.hex, attributes);
     }
 
-    ctx.SleepClientThread("fs_user::open_directly", open_timeout_ns,
-                          [](std::shared_ptr<Kernel::Thread> /*thread*/,
-                             Kernel::HLERequestContext& /*ctx*/,
-                             Kernel::ThreadWakeupReason /*reason*/) {
-                              // Nothing to do here
-                          });
+    ctx.SleepClientThread("fs_user::open_directly", open_timeout_ns, nullptr);
 }
 
 void FS_USER::DeleteFile(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/fs/fs_user.h b/src/core/hle/service/fs/fs_user.h
index 97b45714b..e972d0dae 100644
--- a/src/core/hle/service/fs/fs_user.h
+++ b/src/core/hle/service/fs/fs_user.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <boost/serialization/base_object.hpp>
 #include "common/common_types.h"
 #include "core/hle/service/service.h"
 
@@ -22,6 +23,15 @@ struct ClientSlot : public Kernel::SessionRequestHandler::SessionDataBase {
     // behaviour is modified. Since we don't emulate fs:REG mechanism, we assume the program ID is
     // the same as codeset ID and fetch from there directly.
     u64 program_id = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler::SessionDataBase>(
+            *this);
+        ar& program_id;
+    }
+    friend class boost::serialization::access;
 };
 
 class FS_USER final : public ServiceFramework<FS_USER, ClientSlot> {
@@ -545,8 +555,19 @@ private:
 
     Core::System& system;
     ArchiveManager& archives;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& priority;
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::FS
+
+SERVICE_CONSTRUCT(Service::FS::FS_USER)
+BOOST_CLASS_EXPORT_KEY(Service::FS::FS_USER)
+BOOST_CLASS_EXPORT_KEY(Service::FS::ClientSlot)
diff --git a/src/core/hle/service/gsp/gsp.cpp b/src/core/hle/service/gsp/gsp.cpp
index 4291b9971..c360d895c 100644
--- a/src/core/hle/service/gsp/gsp.cpp
+++ b/src/core/hle/service/gsp/gsp.cpp
@@ -27,4 +27,8 @@ void InstallInterfaces(Core::System& system) {
     std::make_shared<GSP_LCD>()->InstallAsService(service_manager);
 }
 
+void SetGlobalModule(Core::System& system) {
+    gsp_gpu = system.ServiceManager().GetService<GSP_GPU>("gsp::Gpu");
+}
+
 } // namespace Service::GSP
diff --git a/src/core/hle/service/gsp/gsp.h b/src/core/hle/service/gsp/gsp.h
index 4cde19e93..a4dd84f27 100644
--- a/src/core/hle/service/gsp/gsp.h
+++ b/src/core/hle/service/gsp/gsp.h
@@ -23,4 +23,6 @@ namespace Service::GSP {
 void SignalInterrupt(InterruptId interrupt_id);
 
 void InstallInterfaces(Core::System& system);
+
+void SetGlobalModule(Core::System& system);
 } // namespace Service::GSP
diff --git a/src/core/hle/service/gsp/gsp_gpu.cpp b/src/core/hle/service/gsp/gsp_gpu.cpp
index 33558fe10..5a41f5ca0 100644
--- a/src/core/hle/service/gsp/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp/gsp_gpu.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <vector>
+#include "common/archives.h"
 #include "common/bit_field.h"
 #include "common/microprofile.h"
 #include "common/swap.h"
@@ -21,6 +22,10 @@
 #include "video_core/debug_utils/debug_utils.h"
 #include "video_core/gpu_debugger.h"
 
+SERIALIZE_EXPORT_IMPL(Service::GSP::SessionData)
+SERIALIZE_EXPORT_IMPL(Service::GSP::GSP_GPU)
+SERVICE_CONSTRUCT_IMPL(Service::GSP::GSP_GPU)
+
 // Main graphics debugger object - TODO: Here is probably not the best place for this
 GraphicsDebugger g_debugger;
 
@@ -819,10 +824,6 @@ std::unique_ptr<Kernel::SessionRequestHandler::SessionDataBase> GSP_GPU::MakeSes
     return std::make_unique<SessionData>(this);
 }
 
-SessionData::SessionData() {
-    UNREACHABLE();
-}
-
 SessionData::SessionData(GSP_GPU* gsp) : gsp(gsp) {
     // Assign a new thread id to this session when it connects. Note: In the real GSP service this
     // is done through a real thread (svcCreateThread) but we have to simulate it since our HLE
diff --git a/src/core/hle/service/gsp/gsp_gpu.h b/src/core/hle/service/gsp/gsp_gpu.h
index 8b43b9cea..c8914ef72 100644
--- a/src/core/hle/service/gsp/gsp_gpu.h
+++ b/src/core/hle/service/gsp/gsp_gpu.h
@@ -7,6 +7,8 @@
 #include <cstddef>
 #include <memory>
 #include <string>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "core/hle/kernel/event.h"
@@ -187,7 +189,7 @@ class GSP_GPU;
 
 class SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
 public:
-    SessionData();
+    SessionData() = default;
     SessionData(GSP_GPU* gsp);
     ~SessionData();
 
@@ -199,6 +201,18 @@ public:
     u32 thread_id;
     /// Whether RegisterInterruptRelayQueue was called for this session
     bool registered = false;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler::SessionDataBase>(
+            *this);
+        ar& gsp;
+        ar& interrupt_event;
+        ar& thread_id;
+        ar& registered;
+    }
+    friend class boost::serialization::access;
 };
 
 class GSP_GPU final : public ServiceFramework<GSP_GPU, SessionData> {
@@ -431,8 +445,23 @@ private:
     std::array<bool, MaxGSPThreads> used_thread_ids = {false, false, false, false};
 
     friend class SessionData;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& shared_memory;
+        ar& active_thread_id;
+        ar& first_initialization;
+        ar& used_thread_ids;
+    }
+
+    friend class boost::serialization::access;
 };
 
 ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info);
 
 } // namespace Service::GSP
+
+BOOST_CLASS_EXPORT_KEY(Service::GSP::SessionData)
+BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_GPU)
+SERVICE_CONSTRUCT(Service::GSP::GSP_GPU)
diff --git a/src/core/hle/service/gsp/gsp_lcd.cpp b/src/core/hle/service/gsp/gsp_lcd.cpp
index d795b8716..67a2e7628 100644
--- a/src/core/hle/service/gsp/gsp_lcd.cpp
+++ b/src/core/hle/service/gsp/gsp_lcd.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/gsp/gsp_lcd.h"
 
+SERIALIZE_EXPORT_IMPL(Service::GSP::GSP_LCD)
+
 namespace Service::GSP {
 
 GSP_LCD::GSP_LCD() : ServiceFramework("gsp::Lcd") {
diff --git a/src/core/hle/service/gsp/gsp_lcd.h b/src/core/hle/service/gsp/gsp_lcd.h
index 24e57fb42..31d17f540 100644
--- a/src/core/hle/service/gsp/gsp_lcd.h
+++ b/src/core/hle/service/gsp/gsp_lcd.h
@@ -12,6 +12,11 @@ class GSP_LCD final : public ServiceFramework<GSP_LCD> {
 public:
     GSP_LCD();
     ~GSP_LCD() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::GSP
+
+BOOST_CLASS_EXPORT_KEY(Service::GSP::GSP_LCD)
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index ded1809b1..4c8718276 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -4,6 +4,10 @@
 
 #include <algorithm>
 #include <cmath>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unique_ptr.hpp>
+#include "common/archives.h"
 #include "common/logging/log.h"
 #include "core/3ds.h"
 #include "core/core.h"
@@ -20,8 +24,36 @@
 #include "core/movie.h"
 #include "video_core/video_core.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::HID::Module)
+SERIALIZE_EXPORT_IMPL(Service::HID::Module)
+
 namespace Service::HID {
 
+template <class Archive>
+void Module::serialize(Archive& ar, const unsigned int file_version) {
+    ar& shared_mem;
+    ar& event_pad_or_touch_1;
+    ar& event_pad_or_touch_2;
+    ar& event_accelerometer;
+    ar& event_gyroscope;
+    ar& event_debug_pad;
+    ar& next_pad_index;
+    ar& next_touch_index;
+    ar& next_accelerometer_index;
+    ar& next_gyroscope_index;
+    ar& enable_accelerometer_count;
+    ar& enable_gyroscope_count;
+    if (Archive::is_loading::value) {
+        LoadInputDevices();
+    }
+    if (file_version >= 1) {
+        ar& state.hex;
+    }
+    // Update events are set in the constructor
+    // Devices are set from the implementation (and are stateless afaik)
+}
+SERIALIZE_IMPL(Module)
+
 // Updating period for each HID device. These empirical values are measured from a 11.2 3DS.
 constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
 constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 7e8a8c527..58a3c68a5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -8,6 +8,7 @@
 #include <atomic>
 #include <cstddef>
 #include <memory>
+#include <boost/serialization/version.hpp>
 #include "common/bit_field.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
@@ -287,7 +288,7 @@ public:
          */
         void GetGyroscopeLowCalibrateParam(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> hid;
     };
 
@@ -335,9 +336,17 @@ private:
     std::unique_ptr<Input::AnalogDevice> circle_pad;
     std::unique_ptr<Input::MotionDevice> motion_device;
     std::unique_ptr<Input::TouchDevice> touch_device;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 std::shared_ptr<Module> GetModule(Core::System& system);
 
 void InstallInterfaces(Core::System& system);
 } // namespace Service::HID
+
+SERVICE_CONSTRUCT(Service::HID::Module)
+BOOST_CLASS_EXPORT_KEY(Service::HID::Module)
+BOOST_CLASS_VERSION(Service::HID::Module, 1)
diff --git a/src/core/hle/service/hid/hid_spvr.cpp b/src/core/hle/service/hid/hid_spvr.cpp
index 8371a6169..87791f073 100644
--- a/src/core/hle/service/hid/hid_spvr.cpp
+++ b/src/core/hle/service/hid/hid_spvr.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/hid/hid_spvr.h"
 
+SERIALIZE_EXPORT_IMPL(Service::HID::Spvr)
+
 namespace Service::HID {
 
 Spvr::Spvr(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:SPVR", 6) {
diff --git a/src/core/hle/service/hid/hid_spvr.h b/src/core/hle/service/hid/hid_spvr.h
index e2346dda5..749bd36b8 100644
--- a/src/core/hle/service/hid/hid_spvr.h
+++ b/src/core/hle/service/hid/hid_spvr.h
@@ -11,6 +11,12 @@ namespace Service::HID {
 class Spvr final : public Module::Interface {
 public:
     explicit Spvr(std::shared_ptr<Module> hid);
+
+private:
+    SERVICE_SERIALIZATION(Spvr, hid, Module)
 };
 
 } // namespace Service::HID
+
+BOOST_CLASS_EXPORT_KEY(Service::HID::Spvr)
+BOOST_SERIALIZATION_CONSTRUCT(Service::HID::Spvr)
diff --git a/src/core/hle/service/hid/hid_user.cpp b/src/core/hle/service/hid/hid_user.cpp
index 129b3fd02..97ce8f111 100644
--- a/src/core/hle/service/hid/hid_user.cpp
+++ b/src/core/hle/service/hid/hid_user.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/hid/hid_user.h"
 
+SERIALIZE_EXPORT_IMPL(Service::HID::User)
+
 namespace Service::HID {
 
 User::User(std::shared_ptr<Module> hid) : Module::Interface(std::move(hid), "hid:USER", 6) {
diff --git a/src/core/hle/service/hid/hid_user.h b/src/core/hle/service/hid/hid_user.h
index 813f09504..8a6763ea6 100644
--- a/src/core/hle/service/hid/hid_user.h
+++ b/src/core/hle/service/hid/hid_user.h
@@ -14,6 +14,12 @@ namespace Service::HID {
 class User final : public Module::Interface {
 public:
     explicit User(std::shared_ptr<Module> hid);
+
+private:
+    SERVICE_SERIALIZATION(User, hid, Module)
 };
 
 } // namespace Service::HID
+
+BOOST_CLASS_EXPORT_KEY(Service::HID::User)
+BOOST_SERIALIZATION_CONSTRUCT(Service::HID::User)
diff --git a/src/core/hle/service/http_c.cpp b/src/core/hle/service/http_c.cpp
index f0e805e51..4879ab371 100644
--- a/src/core/hle/service/http_c.cpp
+++ b/src/core/hle/service/http_c.cpp
@@ -8,6 +8,7 @@
 #endif
 #include <cryptopp/aes.h>
 #include <cryptopp/modes.h>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "core/core.h"
 #include "core/file_sys/archive_ncch.h"
@@ -19,6 +20,9 @@
 #include "core/hle/service/http_c.h"
 #include "core/hw/aes/key.h"
 
+SERIALIZE_EXPORT_IMPL(Service::HTTP::HTTP_C)
+SERIALIZE_EXPORT_IMPL(Service::HTTP::SessionData)
+
 namespace Service::HTTP {
 
 namespace ErrCodes {
@@ -79,10 +83,10 @@ void Context::MakeRequest() {
         client = std::move(ssl_client);
 
         if (auto client_cert = ssl_config.client_cert_ctx.lock()) {
-            SSL_CTX_use_certificate_ASN1(ctx, client_cert->certificate.size(),
+            SSL_CTX_use_certificate_ASN1(ctx, static_cast<int>(client_cert->certificate.size()),
                                          client_cert->certificate.data());
             SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, ctx, client_cert->private_key.data(),
-                                        client_cert->private_key.size());
+                                        static_cast<long>(client_cert->private_key.size()));
         }
 
         // TODO(B3N30): Check for SSLOptions-Bits and set the verify method accordingly
diff --git a/src/core/hle/service/http_c.h b/src/core/hle/service/http_c.h
index 0742de8a7..490e06648 100644
--- a/src/core/hle/service/http_c.h
+++ b/src/core/hle/service/http_c.h
@@ -10,6 +10,13 @@
 #include <string>
 #include <unordered_map>
 #include <vector>
+#include <boost/optional.hpp>
+#include <boost/serialization/optional.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/unordered_map.hpp>
+#include <boost/serialization/vector.hpp>
+#include <boost/serialization/weak_ptr.hpp>
 #ifdef ENABLE_WEB_SERVICE
 #if defined(__ANDROID__)
 #include <ifaddrs.h>
@@ -57,6 +64,17 @@ struct ClientCertContext {
     u8 cert_id;
     std::vector<u8> certificate;
     std::vector<u8> private_key;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& handle;
+        ar& session_id;
+        ar& cert_id;
+        ar& certificate;
+        ar& private_key;
+    }
+    friend class boost::serialization::access;
 };
 
 /// Represents a root certificate chain, it contains a list of DER-encoded certificates for
@@ -68,12 +86,30 @@ struct RootCertChain {
         Handle handle;
         u32 session_id;
         std::vector<u8> certificate;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& handle;
+            ar& session_id;
+            ar& certificate;
+        }
+        friend class boost::serialization::access;
     };
 
     using Handle = u32;
     Handle handle;
     u32 session_id;
     std::vector<RootCACert> certificates;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& handle;
+        ar& session_id;
+        ar& certificates;
+    }
+    friend class boost::serialization::access;
 };
 
 /// Represents an HTTP context.
@@ -92,30 +128,74 @@ public:
         std::string username;
         std::string password;
         u16 port;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& url;
+            ar& username;
+            ar& password;
+            ar& port;
+        }
+        friend class boost::serialization::access;
     };
 
     struct BasicAuth {
         std::string username;
         std::string password;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& username;
+            ar& password;
+        }
+        friend class boost::serialization::access;
     };
 
     struct RequestHeader {
         RequestHeader(std::string name, std::string value) : name(name), value(value){};
         std::string name;
         std::string value;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& name;
+            ar& value;
+        }
+        friend class boost::serialization::access;
     };
 
     struct PostData {
         // TODO(Subv): Support Binary and Raw POST elements.
         PostData(std::string name, std::string value) : name(name), value(value){};
+        PostData() = default;
         std::string name;
         std::string value;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& name;
+            ar& value;
+        }
+        friend class boost::serialization::access;
     };
 
     struct SSLConfig {
         u32 options;
         std::weak_ptr<ClientCertContext> client_cert_ctx;
         std::weak_ptr<RootCertChain> root_ca_chain;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& options;
+            ar& client_cert_ctx;
+            ar& root_ca_chain;
+        }
+        friend class boost::serialization::access;
     };
 
     Handle handle;
@@ -141,7 +221,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.
-    std::optional<Context::Handle> current_http_context;
+    boost::optional<Context::Handle> current_http_context;
 
     u32 session_id;
 
@@ -153,6 +233,19 @@ struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
     /// Whether this session has been initialized in some way, be it via Initialize or
     /// InitializeConnectionSession.
     bool initialized = false;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler::SessionDataBase>(
+            *this);
+        ar& current_http_context;
+        ar& session_id;
+        ar& num_http_contexts;
+        ar& num_client_certs;
+        ar& initialized;
+    }
+    friend class boost::serialization::access;
 };
 
 class HTTP_C final : public ServiceFramework<HTTP_C, SessionData> {
@@ -339,8 +432,29 @@ private:
         std::vector<u8> private_key;
         bool init = false;
     } ClCertA;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        // NOTE: Serialization of the HTTP service is on a 'best effort' basis.
+        // There is a very good chance that saving/loading during a network connection will break,
+        // regardless!
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& ClCertA.certificate;
+        ar& ClCertA.private_key;
+        ar& ClCertA.init;
+        ar& context_counter;
+        ar& client_certs_counter;
+        ar& client_certs;
+        // NOTE: `contexts` is not serialized because it contains non-serializable data. (i.e.
+        // handles to ongoing HTTP requests.) Serializing across HTTP contexts will break.
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::HTTP
+
+BOOST_CLASS_EXPORT_KEY(Service::HTTP::HTTP_C)
+BOOST_CLASS_EXPORT_KEY(Service::HTTP::SessionData)
diff --git a/src/core/hle/service/ir/extra_hid.h b/src/core/hle/service/ir/extra_hid.h
index d21cb393f..d498c471f 100644
--- a/src/core/hle/service/ir/extra_hid.h
+++ b/src/core/hle/service/ir/extra_hid.h
@@ -6,6 +6,7 @@
 
 #include <array>
 #include <atomic>
+#include <boost/serialization/array.hpp>
 #include "common/bit_field.h"
 #include "common/swap.h"
 #include "core/frontend/input.h"
@@ -65,6 +66,18 @@ private:
     std::unique_ptr<Input::ButtonDevice> zr;
     std::unique_ptr<Input::AnalogDevice> c_stick;
     std::atomic<bool> is_device_reload_pending;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& hid_period;
+        ar& calibration_data; // This isn't writeable for now, but might be in future
+        if (Archive::is_loading::value) {
+            LoadInputDevices(); // zl, zr, c_stick are loaded here
+        }
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::IR
+
+BOOST_CLASS_EXPORT_KEY(Service::IR::ExtraHID)
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 71d16a1ce..bd6c64af3 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -2,6 +2,9 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/hle/ipc_helpers.h"
@@ -12,8 +15,23 @@
 #include "core/movie.h"
 #include "core/settings.h"
 
+SERIALIZE_EXPORT_IMPL(Service::IR::IR_RST)
+SERVICE_CONSTRUCT_IMPL(Service::IR::IR_RST)
+
 namespace Service::IR {
 
+template <class Archive>
+void IR_RST::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar& update_event;
+    ar& shared_memory;
+    ar& next_pad_index;
+    ar& raw_c_stick;
+    ar& update_period;
+    // update_callback_id and input devices are set separately
+    ReloadInputDevices();
+}
+
 struct PadDataEntry {
     PadState current_state;
     PadState delta_additions;
diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h
index 84ad70dfc..8e17381ad 100644
--- a/src/core/hle/service/ir/ir_rst.h
+++ b/src/core/hle/service/ir/ir_rst.h
@@ -87,6 +87,13 @@ private:
     std::atomic<bool> is_device_reload_pending{false};
     bool raw_c_stick{false};
     int update_period{0};
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::IR
+
+BOOST_CLASS_EXPORT_KEY(Service::IR::IR_RST)
+SERVICE_CONSTRUCT(Service::IR::IR_RST)
diff --git a/src/core/hle/service/ir/ir_u.cpp b/src/core/hle/service/ir/ir_u.cpp
index d76323e91..61618869a 100644
--- a/src/core/hle/service/ir/ir_u.cpp
+++ b/src/core/hle/service/ir/ir_u.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ir/ir_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::IR::IR_U)
+
 namespace Service::IR {
 
 IR_U::IR_U() : ServiceFramework("ir:u", 1) {
diff --git a/src/core/hle/service/ir/ir_u.h b/src/core/hle/service/ir/ir_u.h
index ea150b082..ecaf1be28 100644
--- a/src/core/hle/service/ir/ir_u.h
+++ b/src/core/hle/service/ir/ir_u.h
@@ -12,6 +12,11 @@ namespace Service::IR {
 class IR_U final : public ServiceFramework<IR_U> {
 public:
     IR_U();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::IR
+
+BOOST_CLASS_EXPORT_KEY(Service::IR::IR_U)
diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp
index d392c1983..0cc77c928 100644
--- a/src/core/hle/service/ir/ir_user.cpp
+++ b/src/core/hle/service/ir/ir_user.cpp
@@ -4,6 +4,9 @@
 
 #include <memory>
 #include <boost/crc.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/unique_ptr.hpp>
 #include "common/string_util.h"
 #include "common/swap.h"
 #include "core/core.h"
@@ -13,8 +16,23 @@
 #include "core/hle/service/ir/extra_hid.h"
 #include "core/hle/service/ir/ir_user.h"
 
+SERIALIZE_EXPORT_IMPL(Service::IR::IR_USER)
+SERVICE_CONSTRUCT_IMPL(Service::IR::IR_USER)
+
 namespace Service::IR {
 
+template <class Archive>
+void IR_USER::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar& conn_status_event;
+    ar& send_event;
+    ar& receive_event;
+    ar& shared_memory;
+    ar& connected_device;
+    ar& receive_buffer;
+    ar&* extra_hid.get();
+}
+
 // This is a header that will present in the ir:USER shared memory if it is initialized with
 // InitializeIrNopShared service function. Otherwise the shared memory doesn't have this header if
 // it is initialized with InitializeIrNop service function.
@@ -139,6 +157,16 @@ private:
         u32_le end_index;
         u32_le packet_count;
         u32_le unknown;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& begin_index;
+            ar& end_index;
+            ar& packet_count;
+            ar& unknown;
+        }
+        friend class boost::serialization::access;
     };
     static_assert(sizeof(BufferInfo) == 16, "BufferInfo has wrong size!");
 
@@ -179,6 +207,20 @@ private:
     u32 buffer_offset;
     u32 max_packet_count;
     u32 max_data_size;
+
+private:
+    BufferManager() = default;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& info;
+        ar& shared_memory;
+        ar& info_offset;
+        ar& buffer_offset;
+        ar& max_packet_count;
+        ar& max_data_size;
+    }
+    friend class boost::serialization::access;
 };
 
 /// Wraps the payload into packet and puts it to the receive buffer
@@ -270,8 +312,8 @@ void IR_USER::RequireConnection(Kernel::HLERequestContext& ctx) {
         shared_memory_ptr[offsetof(SharedMemoryHeader, connection_role)] = 2;
         shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 1;
 
-        connected_device = extra_hid.get();
-        connected_device->OnConnect();
+        connected_device = true;
+        extra_hid->OnConnect();
         conn_status_event->Signal();
     } else {
         LOG_WARNING(Service_IR, "unknown device id {}. Won't connect.", device_id);
@@ -305,8 +347,8 @@ void IR_USER::GetSendEvent(Kernel::HLERequestContext& ctx) {
 
 void IR_USER::Disconnect(Kernel::HLERequestContext& ctx) {
     if (connected_device) {
-        connected_device->OnDisconnect();
-        connected_device = nullptr;
+        extra_hid->OnDisconnect();
+        connected_device = false;
         conn_status_event->Signal();
     }
 
@@ -331,8 +373,8 @@ void IR_USER::GetConnectionStatusEvent(Kernel::HLERequestContext& ctx) {
 
 void IR_USER::FinalizeIrNop(Kernel::HLERequestContext& ctx) {
     if (connected_device) {
-        connected_device->OnDisconnect();
-        connected_device = nullptr;
+        extra_hid->OnDisconnect();
+        connected_device = false;
     }
 
     shared_memory = nullptr;
@@ -352,7 +394,7 @@ void IR_USER::SendIrNop(Kernel::HLERequestContext& ctx) {
 
     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
     if (connected_device) {
-        connected_device->OnReceive(buffer);
+        extra_hid->OnReceive(buffer);
         send_event->Signal();
         rb.Push(RESULT_SUCCESS);
     } else {
@@ -414,6 +456,7 @@ IR_USER::IR_USER(Core::System& system) : ServiceFramework("ir:USER", 1) {
 
     using namespace Kernel;
 
+    connected_device = false;
     conn_status_event = system.Kernel().CreateEvent(ResetType::OneShot, "IR:ConnectionStatusEvent");
     send_event = system.Kernel().CreateEvent(ResetType::OneShot, "IR:SendEvent");
     receive_event = system.Kernel().CreateEvent(ResetType::OneShot, "IR:ReceiveEvent");
@@ -424,7 +467,7 @@ IR_USER::IR_USER(Core::System& system) : ServiceFramework("ir:USER", 1) {
 
 IR_USER::~IR_USER() {
     if (connected_device) {
-        connected_device->OnDisconnect();
+        extra_hid->OnDisconnect();
     }
 }
 
diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h
index 54a4f3e08..afb9be4f7 100644
--- a/src/core/hle/service/ir/ir_user.h
+++ b/src/core/hle/service/ir/ir_user.h
@@ -45,6 +45,7 @@ protected:
     void Send(const std::vector<u8>& data);
 
 private:
+    // NOTE: This value is *not* serialized because it's always passed in the constructor
     const SendFunc send_func;
 };
 
@@ -164,9 +165,17 @@ private:
 
     std::shared_ptr<Kernel::Event> conn_status_event, send_event, receive_event;
     std::shared_ptr<Kernel::SharedMemory> shared_memory;
-    IRDevice* connected_device{nullptr};
+    bool connected_device;
     std::unique_ptr<BufferManager> receive_buffer;
     std::unique_ptr<ExtraHID> extra_hid;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::IR
+
+BOOST_CLASS_EXPORT_KEY(Service::IR::IR_USER)
+SERVICE_CONSTRUCT(Service::IR::IR_USER)
diff --git a/src/core/hle/service/ldr_ro/ldr_ro.cpp b/src/core/hle/service/ldr_ro/ldr_ro.cpp
index a435d5e58..a2f8379b1 100644
--- a/src/core/hle/service/ldr_ro/ldr_ro.cpp
+++ b/src/core/hle/service/ldr_ro/ldr_ro.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include "common/alignment.h"
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/arm/arm_interface.h"
@@ -12,6 +13,10 @@
 #include "core/hle/service/ldr_ro/cro_helper.h"
 #include "core/hle/service/ldr_ro/ldr_ro.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::LDR::RO)
+SERIALIZE_EXPORT_IMPL(Service::LDR::RO)
+SERIALIZE_EXPORT_IMPL(Service::LDR::ClientSlot)
+
 namespace Service::LDR {
 
 static const ResultCode ERROR_ALREADY_INITIALIZED = // 0xD9612FF9
diff --git a/src/core/hle/service/ldr_ro/ldr_ro.h b/src/core/hle/service/ldr_ro/ldr_ro.h
index f90005d13..7581884f6 100644
--- a/src/core/hle/service/ldr_ro/ldr_ro.h
+++ b/src/core/hle/service/ldr_ro/ldr_ro.h
@@ -14,6 +14,15 @@ namespace Service::LDR {
 
 struct ClientSlot : public Kernel::SessionRequestHandler::SessionDataBase {
     VAddr loaded_crs = 0; ///< the virtual address of the static module
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler::SessionDataBase>(
+            *this);
+        ar& loaded_crs;
+    }
+    friend class boost::serialization::access;
 };
 
 class RO final : public ServiceFramework<RO, ClientSlot> {
@@ -151,8 +160,19 @@ private:
     void Shutdown(Kernel::HLERequestContext& self);
 
     Core::System& system;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::LDR
+
+SERVICE_CONSTRUCT(Service::LDR::RO)
+BOOST_CLASS_EXPORT_KEY(Service::LDR::RO)
+BOOST_CLASS_EXPORT_KEY(Service::LDR::ClientSlot)
diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp
index ba3e97807..f0017ac4c 100644
--- a/src/core/hle/service/mic_u.cpp
+++ b/src/core/hle/service/mic_u.cpp
@@ -2,9 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/weak_ptr.hpp>
 #ifdef HAVE_CUBEB
 #include "audio_core/cubeb_input.h"
 #endif
+#include "common/archives.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/frontend/mic.h"
@@ -17,8 +19,17 @@
 #include "core/hle/service/mic_u.h"
 #include "core/settings.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::MIC::MIC_U)
+SERIALIZE_EXPORT_IMPL(Service::MIC::MIC_U)
+
 namespace Service::MIC {
 
+template <class Archive>
+void MIC_U::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar&* impl.get();
+}
+
 /// Microphone audio encodings.
 enum class Encoding : u8 {
     PCM8 = 0,        ///< Unsigned 8-bit PCM.
@@ -59,6 +70,7 @@ constexpr u64 GetBufferUpdateRate(SampleRate sample_rate) {
 
 // Variables holding the current mic buffer writing state
 struct State {
+    std::weak_ptr<Kernel::SharedMemory> memory_ref{};
     u8* sharedmem_buffer = nullptr;
     u32 sharedmem_size = 0;
     std::size_t size = 0;
@@ -95,6 +107,23 @@ struct State {
         std::memcpy(sharedmem_buffer + (sharedmem_size - sizeof(u32)), reinterpret_cast<u8*>(&off),
                     sizeof(u32));
     }
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        std::shared_ptr<Kernel::SharedMemory> _memory_ref = memory_ref.lock();
+        ar& _memory_ref;
+        memory_ref = _memory_ref;
+        ar& sharedmem_size;
+        ar& size;
+        ar& offset;
+        ar& initial_offset;
+        ar& looped_buffer;
+        ar& sample_size;
+        ar& sample_rate;
+        sharedmem_buffer = _memory_ref ? _memory_ref->GetPointer() : nullptr;
+    }
+    friend class boost::serialization::access;
 };
 
 struct MIC_U::Impl {
@@ -114,6 +143,7 @@ struct MIC_U::Impl {
 
         if (shared_memory) {
             shared_memory->SetName("MIC_U:shared_memory");
+            state.memory_ref = shared_memory;
             state.sharedmem_buffer = shared_memory->GetPointer();
             state.sharedmem_size = size;
         }
@@ -363,6 +393,21 @@ struct MIC_U::Impl {
     std::unique_ptr<Frontend::Mic::Interface> mic;
     Core::Timing& timing;
     State state{};
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& change_mic_impl_requested;
+        ar& buffer_full_event;
+        // buffer_write_event set in constructor
+        ar& shared_memory;
+        ar& client_version;
+        ar& allow_shell_closed;
+        ar& clamp;
+        // mic interface set in constructor
+        ar& state;
+    }
+    friend class boost::serialization::access;
 };
 
 void MIC_U::MapSharedMem(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/mic_u.h b/src/core/hle/service/mic_u.h
index 2e40ed404..2ca95e924 100644
--- a/src/core/hle/service/mic_u.h
+++ b/src/core/hle/service/mic_u.h
@@ -190,6 +190,10 @@ private:
 
     struct Impl;
     std::unique_ptr<Impl> impl;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 void ReloadMic(Core::System& system);
@@ -197,3 +201,6 @@ void ReloadMic(Core::System& system);
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::MIC
+
+SERVICE_CONSTRUCT(Service::MIC::MIC_U)
+BOOST_CLASS_EXPORT_KEY(Service::MIC::MIC_U)
diff --git a/src/core/hle/service/mvd/mvd_std.cpp b/src/core/hle/service/mvd/mvd_std.cpp
index 2c397f2dd..43dbde584 100644
--- a/src/core/hle/service/mvd/mvd_std.cpp
+++ b/src/core/hle/service/mvd/mvd_std.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/mvd/mvd_std.h"
 
+SERIALIZE_EXPORT_IMPL(Service::MVD::MVD_STD)
+
 namespace Service::MVD {
 
 MVD_STD::MVD_STD() : ServiceFramework("mvd:std", 1) {
diff --git a/src/core/hle/service/mvd/mvd_std.h b/src/core/hle/service/mvd/mvd_std.h
index 6764f6ba8..fed41e6f0 100644
--- a/src/core/hle/service/mvd/mvd_std.h
+++ b/src/core/hle/service/mvd/mvd_std.h
@@ -12,6 +12,11 @@ class MVD_STD final : public ServiceFramework<MVD_STD> {
 public:
     MVD_STD();
     ~MVD_STD() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::MVD
+
+BOOST_CLASS_EXPORT_KEY(Service::MVD::MVD_STD)
diff --git a/src/core/hle/service/ndm/ndm_u.cpp b/src/core/hle/service/ndm/ndm_u.cpp
index 057e68ede..936d53799 100644
--- a/src/core/hle/service/ndm/ndm_u.cpp
+++ b/src/core/hle/service/ndm/ndm_u.cpp
@@ -2,10 +2,13 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/ndm/ndm_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NDM::NDM_U)
+
 namespace Service::NDM {
 
 void NDM_U::EnterExclusiveState(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/ndm/ndm_u.h b/src/core/hle/service/ndm/ndm_u.h
index 5f48a3182..aebc6fa8b 100644
--- a/src/core/hle/service/ndm/ndm_u.h
+++ b/src/core/hle/service/ndm/ndm_u.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <array>
+#include <boost/serialization/array.hpp>
 #include "core/hle/service/service.h"
 
 namespace Core {
@@ -270,8 +271,23 @@ private:
     u32 scan_interval = DEFAULT_SCAN_INTERVAL;
     u32 retry_interval = DEFAULT_RETRY_INTERVAL;
     bool daemon_lock_enabled = false;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& daemon_bit_mask;
+        ar& default_daemon_bit_mask;
+        ar& daemon_status;
+        ar& exclusive_state;
+        ar& scan_interval;
+        ar& retry_interval;
+        ar& daemon_lock_enabled;
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::NDM
+
+BOOST_CLASS_EXPORT_KEY(Service::NDM::NDM_U)
diff --git a/src/core/hle/service/news/news_s.cpp b/src/core/hle/service/news/news_s.cpp
index 17eaa5ea8..94cf68f4c 100644
--- a/src/core/hle/service/news/news_s.cpp
+++ b/src/core/hle/service/news/news_s.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/news/news_s.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NEWS::NEWS_S)
+
 namespace Service::NEWS {
 
 void NEWS_S::GetTotalNotifications(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/news/news_s.h b/src/core/hle/service/news/news_s.h
index e4673d22c..9d1ce829f 100644
--- a/src/core/hle/service/news/news_s.h
+++ b/src/core/hle/service/news/news_s.h
@@ -24,6 +24,10 @@ private:
      *      2 : Number of notifications
      */
     void GetTotalNotifications(Kernel::HLERequestContext& ctx);
+
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NEWS
+
+BOOST_CLASS_EXPORT_KEY(Service::NEWS::NEWS_S)
diff --git a/src/core/hle/service/news/news_u.cpp b/src/core/hle/service/news/news_u.cpp
index 3d6e87f91..d91b594b8 100644
--- a/src/core/hle/service/news/news_u.cpp
+++ b/src/core/hle/service/news/news_u.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/news/news_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NEWS::NEWS_U)
+
 namespace Service::NEWS {
 
 NEWS_U::NEWS_U() : ServiceFramework("news:u", 1) {
diff --git a/src/core/hle/service/news/news_u.h b/src/core/hle/service/news/news_u.h
index cb06bad39..8e672256d 100644
--- a/src/core/hle/service/news/news_u.h
+++ b/src/core/hle/service/news/news_u.h
@@ -12,6 +12,11 @@ namespace Service::NEWS {
 class NEWS_U final : public ServiceFramework<NEWS_U> {
 public:
     NEWS_U();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NEWS
+
+BOOST_CLASS_EXPORT_KEY(Service::NEWS::NEWS_U)
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 3610b5416..ed9cbb0a3 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -2,6 +2,7 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/event.h"
@@ -10,8 +11,22 @@
 #include "core/hle/service/nfc/nfc_m.h"
 #include "core/hle/service/nfc/nfc_u.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::NFC::Module)
+SERIALIZE_EXPORT_IMPL(Service::NFC::Module)
+
 namespace Service::NFC {
 
+template <class Archive>
+void Module::serialize(Archive& ar, const unsigned int) {
+    ar& tag_in_range_event;
+    ar& tag_out_of_range_event;
+    ar& nfc_tag_state;
+    ar& nfc_status;
+    ar& amiibo_data;
+    ar& amiibo_in_range;
+}
+SERIALIZE_IMPL(Module)
+
 struct TagInfo {
     u16_le id_offset_size;
     u8 unk1;
diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h
index 64bd239ac..51fe20b76 100644
--- a/src/core/hle/service/nfc/nfc.h
+++ b/src/core/hle/service/nfc/nfc.h
@@ -6,6 +6,7 @@
 
 #include <atomic>
 #include <memory>
+#include <boost/serialization/binary_object.hpp>
 #include "common/common_types.h"
 #include "core/hle/service/service.h"
 
@@ -35,6 +36,13 @@ struct AmiiboData {
     u16_be model_number;
     u8 series;
     INSERT_PADDING_BYTES(0x1C1);
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::make_binary_object(this, sizeof(AmiiboData));
+    }
+    friend class boost::serialization::access;
 };
 static_assert(sizeof(AmiiboData) == 0x21C, "AmiiboData is an invalid size");
 
@@ -226,7 +234,7 @@ public:
          */
         void GetIdentificationBlock(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> nfc;
     };
 
@@ -241,8 +249,15 @@ private:
 
     AmiiboData amiibo_data{};
     bool amiibo_in_range = false;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::NFC
+
+SERVICE_CONSTRUCT(Service::NFC::Module)
+BOOST_CLASS_EXPORT_KEY(Service::NFC::Module)
diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp
index 310490b8f..cd591b9c1 100644
--- a/src/core/hle/service/nfc/nfc_m.cpp
+++ b/src/core/hle/service/nfc/nfc_m.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nfc/nfc_m.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NFC::NFC_M)
+
 namespace Service::NFC {
 
 NFC_M::NFC_M(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "nfc:m", 1) {
diff --git a/src/core/hle/service/nfc/nfc_m.h b/src/core/hle/service/nfc/nfc_m.h
index c9fe9b130..48a9e241b 100644
--- a/src/core/hle/service/nfc/nfc_m.h
+++ b/src/core/hle/service/nfc/nfc_m.h
@@ -11,6 +11,12 @@ namespace Service::NFC {
 class NFC_M final : public Module::Interface {
 public:
     explicit NFC_M(std::shared_ptr<Module> nfc);
+
+private:
+    SERVICE_SERIALIZATION(NFC_M, nfc, Module)
 };
 
 } // namespace Service::NFC
+
+BOOST_CLASS_EXPORT_KEY(Service::NFC::NFC_M)
+BOOST_SERIALIZATION_CONSTRUCT(Service::NFC::NFC_M)
diff --git a/src/core/hle/service/nfc/nfc_u.cpp b/src/core/hle/service/nfc/nfc_u.cpp
index a6e99ace2..58d1843e4 100644
--- a/src/core/hle/service/nfc/nfc_u.cpp
+++ b/src/core/hle/service/nfc/nfc_u.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nfc/nfc_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NFC::NFC_U)
+
 namespace Service::NFC {
 
 NFC_U::NFC_U(std::shared_ptr<Module> nfc) : Module::Interface(std::move(nfc), "nfc:u", 1) {
diff --git a/src/core/hle/service/nfc/nfc_u.h b/src/core/hle/service/nfc/nfc_u.h
index aab408269..2ed6030e6 100644
--- a/src/core/hle/service/nfc/nfc_u.h
+++ b/src/core/hle/service/nfc/nfc_u.h
@@ -11,6 +11,12 @@ namespace Service::NFC {
 class NFC_U final : public Module::Interface {
 public:
     explicit NFC_U(std::shared_ptr<Module> nfc);
+
+private:
+    SERVICE_SERIALIZATION(NFC_U, nfc, Module)
 };
 
 } // namespace Service::NFC
+
+BOOST_CLASS_EXPORT_KEY(Service::NFC::NFC_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::NFC::NFC_U)
diff --git a/src/core/hle/service/nim/nim_aoc.cpp b/src/core/hle/service/nim/nim_aoc.cpp
index ddd8d5e03..f20c96f69 100644
--- a/src/core/hle/service/nim/nim_aoc.cpp
+++ b/src/core/hle/service/nim/nim_aoc.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nim/nim_aoc.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NIM::NIM_AOC)
+
 namespace Service::NIM {
 
 NIM_AOC::NIM_AOC() : ServiceFramework("nim:aoc", 2) {
diff --git a/src/core/hle/service/nim/nim_aoc.h b/src/core/hle/service/nim/nim_aoc.h
index 5a1f518ec..003b3fd85 100644
--- a/src/core/hle/service/nim/nim_aoc.h
+++ b/src/core/hle/service/nim/nim_aoc.h
@@ -12,6 +12,11 @@ class NIM_AOC final : public ServiceFramework<NIM_AOC> {
 public:
     NIM_AOC();
     ~NIM_AOC();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NIM
+
+BOOST_CLASS_EXPORT_KEY(Service::NIM::NIM_AOC)
diff --git a/src/core/hle/service/nim/nim_s.cpp b/src/core/hle/service/nim/nim_s.cpp
index d7236249f..27118e406 100644
--- a/src/core/hle/service/nim/nim_s.cpp
+++ b/src/core/hle/service/nim/nim_s.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nim/nim_s.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NIM::NIM_S)
+
 namespace Service::NIM {
 
 NIM_S::NIM_S() : ServiceFramework("nim:s", 1) {
diff --git a/src/core/hle/service/nim/nim_s.h b/src/core/hle/service/nim/nim_s.h
index 6485cde18..10b041456 100644
--- a/src/core/hle/service/nim/nim_s.h
+++ b/src/core/hle/service/nim/nim_s.h
@@ -12,6 +12,11 @@ class NIM_S final : public ServiceFramework<NIM_S> {
 public:
     NIM_S();
     ~NIM_S();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NIM
+
+BOOST_CLASS_EXPORT_KEY(Service::NIM::NIM_S)
diff --git a/src/core/hle/service/nim/nim_u.cpp b/src/core/hle/service/nim/nim_u.cpp
index dd0e4d31c..b44ef3539 100644
--- a/src/core/hle/service/nim/nim_u.cpp
+++ b/src/core/hle/service/nim/nim_u.cpp
@@ -2,11 +2,15 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/kernel/event.h"
 #include "core/hle/service/nim/nim_u.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::NIM::NIM_U)
+SERIALIZE_EXPORT_IMPL(Service::NIM::NIM_U)
+
 namespace Service::NIM {
 
 NIM_U::NIM_U(Core::System& system) : ServiceFramework("nim:u", 2) {
diff --git a/src/core/hle/service/nim/nim_u.h b/src/core/hle/service/nim/nim_u.h
index 367ee6ea8..98fec69b2 100644
--- a/src/core/hle/service/nim/nim_u.h
+++ b/src/core/hle/service/nim/nim_u.h
@@ -41,6 +41,16 @@ private:
     void CheckSysUpdateAvailable(Kernel::HLERequestContext& ctx);
 
     std::shared_ptr<Kernel::Event> nim_system_update_event;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& nim_system_update_event;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::NIM
+
+SERVICE_CONSTRUCT(Service::NIM::NIM_U)
+BOOST_CLASS_EXPORT_KEY(Service::NIM::NIM_U)
diff --git a/src/core/hle/service/nwm/nwm_cec.cpp b/src/core/hle/service/nwm/nwm_cec.cpp
index 7c47c88c7..ecd4f16e1 100644
--- a/src/core/hle/service/nwm/nwm_cec.cpp
+++ b/src/core/hle/service/nwm/nwm_cec.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nwm/nwm_cec.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_CEC)
+
 namespace Service::NWM {
 
 NWM_CEC::NWM_CEC() : ServiceFramework("nwm::CEC") {
diff --git a/src/core/hle/service/nwm/nwm_cec.h b/src/core/hle/service/nwm/nwm_cec.h
index afdf98477..674c98cae 100644
--- a/src/core/hle/service/nwm/nwm_cec.h
+++ b/src/core/hle/service/nwm/nwm_cec.h
@@ -11,6 +11,11 @@ namespace Service::NWM {
 class NWM_CEC final : public ServiceFramework<NWM_CEC> {
 public:
     NWM_CEC();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NWM
+
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_CEC)
diff --git a/src/core/hle/service/nwm/nwm_ext.cpp b/src/core/hle/service/nwm/nwm_ext.cpp
index 4bbac391f..d69da94ff 100644
--- a/src/core/hle/service/nwm/nwm_ext.cpp
+++ b/src/core/hle/service/nwm/nwm_ext.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nwm/nwm_ext.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_EXT)
+
 namespace Service::NWM {
 
 NWM_EXT::NWM_EXT() : ServiceFramework("nwm::EXT") {
diff --git a/src/core/hle/service/nwm/nwm_ext.h b/src/core/hle/service/nwm/nwm_ext.h
index 1711db65a..1e8bcfde3 100644
--- a/src/core/hle/service/nwm/nwm_ext.h
+++ b/src/core/hle/service/nwm/nwm_ext.h
@@ -11,6 +11,11 @@ namespace Service::NWM {
 class NWM_EXT final : public ServiceFramework<NWM_EXT> {
 public:
     NWM_EXT();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NWM
+
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_EXT)
diff --git a/src/core/hle/service/nwm/nwm_inf.cpp b/src/core/hle/service/nwm/nwm_inf.cpp
index 71cf11891..eaabf6667 100644
--- a/src/core/hle/service/nwm/nwm_inf.cpp
+++ b/src/core/hle/service/nwm/nwm_inf.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nwm/nwm_inf.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_INF)
+
 namespace Service::NWM {
 
 NWM_INF::NWM_INF() : ServiceFramework("nwm::INF") {
diff --git a/src/core/hle/service/nwm/nwm_inf.h b/src/core/hle/service/nwm/nwm_inf.h
index 2c69cfb1e..9f8c65a2b 100644
--- a/src/core/hle/service/nwm/nwm_inf.h
+++ b/src/core/hle/service/nwm/nwm_inf.h
@@ -11,6 +11,11 @@ namespace Service::NWM {
 class NWM_INF final : public ServiceFramework<NWM_INF> {
 public:
     NWM_INF();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NWM
+
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_INF)
diff --git a/src/core/hle/service/nwm/nwm_sap.cpp b/src/core/hle/service/nwm/nwm_sap.cpp
index 2ef196ab4..2cedf9371 100644
--- a/src/core/hle/service/nwm/nwm_sap.cpp
+++ b/src/core/hle/service/nwm/nwm_sap.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nwm/nwm_sap.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_SAP)
+
 namespace Service::NWM {
 
 NWM_SAP::NWM_SAP() : ServiceFramework("nwm::SAP") {
diff --git a/src/core/hle/service/nwm/nwm_sap.h b/src/core/hle/service/nwm/nwm_sap.h
index b6700b8ed..0422dc658 100644
--- a/src/core/hle/service/nwm/nwm_sap.h
+++ b/src/core/hle/service/nwm/nwm_sap.h
@@ -11,6 +11,11 @@ namespace Service::NWM {
 class NWM_SAP final : public ServiceFramework<NWM_SAP> {
 public:
     NWM_SAP();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NWM
+
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_SAP)
diff --git a/src/core/hle/service/nwm/nwm_soc.cpp b/src/core/hle/service/nwm/nwm_soc.cpp
index 443baaf39..d6ca365ab 100644
--- a/src/core/hle/service/nwm/nwm_soc.cpp
+++ b/src/core/hle/service/nwm/nwm_soc.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nwm/nwm_soc.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_SOC)
+
 namespace Service::NWM {
 
 NWM_SOC::NWM_SOC() : ServiceFramework("nwm::SOC") {
diff --git a/src/core/hle/service/nwm/nwm_soc.h b/src/core/hle/service/nwm/nwm_soc.h
index 8e1b922bc..f13490e83 100644
--- a/src/core/hle/service/nwm/nwm_soc.h
+++ b/src/core/hle/service/nwm/nwm_soc.h
@@ -11,6 +11,11 @@ namespace Service::NWM {
 class NWM_SOC final : public ServiceFramework<NWM_SOC> {
 public:
     NWM_SOC();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NWM
+
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_SOC)
diff --git a/src/core/hle/service/nwm/nwm_tst.cpp b/src/core/hle/service/nwm/nwm_tst.cpp
index 3be65200b..65ffabfd9 100644
--- a/src/core/hle/service/nwm/nwm_tst.cpp
+++ b/src/core/hle/service/nwm/nwm_tst.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/nwm/nwm_tst.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_TST)
+
 namespace Service::NWM {
 
 NWM_TST::NWM_TST() : ServiceFramework("nwm::TST") {
diff --git a/src/core/hle/service/nwm/nwm_tst.h b/src/core/hle/service/nwm/nwm_tst.h
index 8214e0d1d..576d4d124 100644
--- a/src/core/hle/service/nwm/nwm_tst.h
+++ b/src/core/hle/service/nwm/nwm_tst.h
@@ -11,6 +11,11 @@ namespace Service::NWM {
 class NWM_TST final : public ServiceFramework<NWM_TST> {
 public:
     NWM_TST();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::NWM
+
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_TST)
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index fe8f7635f..cba699f25 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -4,7 +4,10 @@
 
 #include <algorithm>
 #include <cstring>
+#include <boost/serialization/list.hpp>
+#include <boost/serialization/map.hpp>
 #include <cryptopp/osrng.h>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -21,8 +24,20 @@
 #include "core/hle/service/nwm/uds_data.h"
 #include "core/memory.h"
 
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_UDS)
+SERVICE_CONSTRUCT_IMPL(Service::NWM::NWM_UDS)
+
 namespace Service::NWM {
 
+template <class Archive>
+void NWM_UDS::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar& node_map;
+    ar& connection_event;
+    ar& received_beacons;
+    // wifi_packet_received set in constructor
+}
+
 namespace ErrCodes {
 enum {
     NotInitialized = 2,
@@ -489,6 +504,9 @@ void NWM_UDS::HandleDataFrame(const Network::WifiPacket& packet) {
 
 /// Callback to parse and handle a received wifi packet.
 void NWM_UDS::OnWifiPacketReceived(const Network::WifiPacket& packet) {
+    if (!initialized) {
+        return;
+    }
     switch (packet.type) {
     case Network::WifiPacket::PacketType::Beacon:
         HandleBeaconFrame(packet);
@@ -534,8 +552,7 @@ boost::optional<Network::MacAddress> NWM_UDS::GetNodeMacAddress(u16 dest_node_id
 void NWM_UDS::Shutdown(Kernel::HLERequestContext& ctx) {
     IPC::RequestParser rp(ctx, 0x03, 0, 0);
 
-    if (auto room_member = Network::GetRoomMember().lock())
-        room_member->Unbind(wifi_packet_received);
+    initialized = false;
 
     for (auto bind_node : channel_data) {
         bind_node.second.event->Signal();
@@ -625,13 +642,6 @@ ResultVal<std::shared_ptr<Kernel::Event>> NWM_UDS::Initialize(
     recv_buffer_memory = std::move(sharedmem);
     ASSERT_MSG(recv_buffer_memory->GetSize() == sharedmem_size, "Invalid shared memory size.");
 
-    if (auto room_member = Network::GetRoomMember().lock()) {
-        wifi_packet_received = room_member->BindOnWifiPacketReceived(
-            [this](const Network::WifiPacket& packet) { OnWifiPacketReceived(packet); });
-    } else {
-        LOG_ERROR(Service_NWM, "Network isn't initalized");
-    }
-
     {
         std::lock_guard lock(connection_status_mutex);
 
@@ -1165,6 +1175,30 @@ void NWM_UDS::GetChannel(Kernel::HLERequestContext& ctx) {
     LOG_DEBUG(Service_NWM, "called");
 }
 
+class NWM_UDS::ThreadCallback : public Kernel::HLERequestContext::WakeupCallback {
+public:
+    explicit ThreadCallback(u16 command_id_) : command_id(command_id_) {}
+
+    void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
+                Kernel::ThreadWakeupReason reason) {
+        // TODO(B3N30): Add error handling for host full and timeout
+        IPC::RequestBuilder rb(ctx, command_id, 1, 0);
+        rb.Push(RESULT_SUCCESS);
+        LOG_DEBUG(Service_NWM, "connection sequence finished");
+    }
+
+private:
+    ThreadCallback() = default;
+    u16 command_id;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::HLERequestContext::WakeupCallback>(*this);
+        ar& command_id;
+    }
+    friend class boost::serialization::access;
+};
+
 void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id,
                                const u8* network_info_buffer, std::size_t network_info_size,
                                u8 connection_type, std::vector<u8> passphrase) {
@@ -1178,15 +1212,8 @@ void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx, u16 command_id,
     // Since this timing is handled by core_timing it could differ from the 'real world' time
     static constexpr std::chrono::nanoseconds UDSConnectionTimeout{300000000};
 
-    connection_event = ctx.SleepClientThread(
-        "uds::ConnectToNetwork", UDSConnectionTimeout,
-        [command_id](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
-                     Kernel::ThreadWakeupReason reason) {
-            // TODO(B3N30): Add error handling for host full and timeout
-            IPC::RequestBuilder rb(ctx, command_id, 1, 0);
-            rb.Push(RESULT_SUCCESS);
-            LOG_DEBUG(Service_NWM, "connection sequence finished");
-        });
+    connection_event = ctx.SleepClientThread("uds::ConnectToNetwork", UDSConnectionTimeout,
+                                             std::make_shared<ThreadCallback>(command_id));
 }
 
 void NWM_UDS::ConnectToNetwork(Kernel::HLERequestContext& ctx) {
@@ -1396,6 +1423,13 @@ NWM_UDS::NWM_UDS(Core::System& system) : ServiceFramework("nwm::UDS"), system(sy
 
     system.Kernel().GetSharedPageHandler().SetMacAddress(mac);
     system.Kernel().GetSharedPageHandler().SetWifiLinkLevel(SharedPage::WifiLinkLevel::BEST);
+
+    if (auto room_member = Network::GetRoomMember().lock()) {
+        wifi_packet_received = room_member->BindOnWifiPacketReceived(
+            [this](const Network::WifiPacket& packet) { OnWifiPacketReceived(packet); });
+    } else {
+        LOG_ERROR(Service_NWM, "Network isn't initalized");
+    }
 }
 
 NWM_UDS::~NWM_UDS() {
@@ -1406,3 +1440,5 @@ NWM_UDS::~NWM_UDS() {
 }
 
 } // namespace Service::NWM
+
+SERIALIZE_EXPORT_IMPL(Service::NWM::NWM_UDS::ThreadCallback)
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h
index 52ca38663..7ae471a53 100644
--- a/src/core/hle/service/nwm/nwm_uds.h
+++ b/src/core/hle/service/nwm/nwm_uds.h
@@ -15,6 +15,7 @@
 #include <unordered_map>
 #include <vector>
 #include <boost/optional.hpp>
+#include <boost/serialization/export.hpp>
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/hle/service/service.h"
@@ -127,6 +128,8 @@ public:
     explicit NWM_UDS(Core::System& system);
     ~NWM_UDS();
 
+    class ThreadCallback;
+
 private:
     Core::System& system;
 
@@ -521,6 +524,14 @@ private:
     struct Node {
         bool connected;
         u16 node_id;
+
+    private:
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int) {
+            ar& connected;
+            ar& node_id;
+        }
+        friend class boost::serialization::access;
     };
 
     std::map<MacAddress, Node> node_map;
@@ -543,6 +554,14 @@ private:
 
     // List of the last <MaxBeaconFrames> beacons received from the network.
     std::list<Network::WifiPacket> received_beacons;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::NWM
+
+SERVICE_CONSTRUCT(Service::NWM::NWM_UDS)
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_UDS)
+BOOST_CLASS_EXPORT_KEY(Service::NWM::NWM_UDS::ThreadCallback)
diff --git a/src/core/hle/service/pm/pm_app.cpp b/src/core/hle/service/pm/pm_app.cpp
index 9599dfcfc..fd0858356 100644
--- a/src/core/hle/service/pm/pm_app.cpp
+++ b/src/core/hle/service/pm/pm_app.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/pm/pm_app.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PM::PM_APP)
+
 namespace Service::PM {
 
 PM_APP::PM_APP() : ServiceFramework("pm:app", 3) {
diff --git a/src/core/hle/service/pm/pm_app.h b/src/core/hle/service/pm/pm_app.h
index 8c7e375f0..9aefb0cee 100644
--- a/src/core/hle/service/pm/pm_app.h
+++ b/src/core/hle/service/pm/pm_app.h
@@ -12,6 +12,11 @@ class PM_APP final : public ServiceFramework<PM_APP> {
 public:
     PM_APP();
     ~PM_APP() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::PM
+
+BOOST_CLASS_EXPORT_KEY(Service::PM::PM_APP)
diff --git a/src/core/hle/service/pm/pm_dbg.cpp b/src/core/hle/service/pm/pm_dbg.cpp
index 63879ff20..33e195a6f 100644
--- a/src/core/hle/service/pm/pm_dbg.cpp
+++ b/src/core/hle/service/pm/pm_dbg.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/pm/pm_dbg.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PM::PM_DBG)
+
 namespace Service::PM {
 
 PM_DBG::PM_DBG() : ServiceFramework("pm:dbg", 3) {
diff --git a/src/core/hle/service/pm/pm_dbg.h b/src/core/hle/service/pm/pm_dbg.h
index 77b644969..3a6e3c5c7 100644
--- a/src/core/hle/service/pm/pm_dbg.h
+++ b/src/core/hle/service/pm/pm_dbg.h
@@ -12,6 +12,11 @@ class PM_DBG final : public ServiceFramework<PM_DBG> {
 public:
     PM_DBG();
     ~PM_DBG() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::PM
+
+BOOST_CLASS_EXPORT_KEY(Service::PM::PM_DBG)
diff --git a/src/core/hle/service/ps/ps_ps.cpp b/src/core/hle/service/ps/ps_ps.cpp
index a48903348..1ba4d7f4c 100644
--- a/src/core/hle/service/ps/ps_ps.cpp
+++ b/src/core/hle/service/ps/ps_ps.cpp
@@ -4,6 +4,7 @@
 
 #include <cryptopp/aes.h>
 #include <cryptopp/modes.h>
+#include "common/archives.h"
 #include "common/logging/log.h"
 #include "core/core.h"
 #include "core/hle/ipc_helpers.h"
@@ -11,6 +12,8 @@
 #include "core/hw/aes/arithmetic128.h"
 #include "core/hw/aes/key.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PS::PS_PS)
+
 namespace Service::PS {
 
 enum class AlgorithmType : u8 {
diff --git a/src/core/hle/service/ps/ps_ps.h b/src/core/hle/service/ps/ps_ps.h
index 6e8b3ad0a..f5005d444 100644
--- a/src/core/hle/service/ps/ps_ps.h
+++ b/src/core/hle/service/ps/ps_ps.h
@@ -18,6 +18,8 @@ public:
     ~PS_PS() = default;
 
 private:
+    SERVICE_SERIALIZATION_SIMPLE
+
     /**
      * PS_PS::SignRsaSha256 service function
      *  Inputs:
@@ -231,3 +233,5 @@ private:
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::PS
+
+BOOST_CLASS_EXPORT_KEY(Service::PS::PS_PS)
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 4af8291bf..430d48cdd 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <cinttypes>
+#include "common/archives.h"
 #include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
@@ -18,6 +19,8 @@
 #include "core/hle/service/ptm/ptm_u.h"
 #include "core/settings.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PTM::Module)
+
 namespace Service::PTM {
 
 /// Values for the default gamecoin.dat file
diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h
index 549e69353..ba23224d7 100644
--- a/src/core/hle/service/ptm/ptm.h
+++ b/src/core/hle/service/ptm/ptm.h
@@ -137,7 +137,7 @@ public:
          */
         void CheckNew3DS(Kernel::HLERequestContext& ctx);
 
-    private:
+    protected:
         std::shared_ptr<Module> ptm;
     };
 
@@ -145,8 +145,18 @@ private:
     bool shell_open = true;
     bool battery_is_charging = true;
     bool pedometer_is_counting = false;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& shell_open;
+        ar& battery_is_charging;
+        ar& pedometer_is_counting;
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::PTM
+
+BOOST_CLASS_EXPORT_KEY(Service::PTM::Module)
diff --git a/src/core/hle/service/ptm/ptm_gets.cpp b/src/core/hle/service/ptm/ptm_gets.cpp
index e083aed6a..6feedbf94 100644
--- a/src/core/hle/service/ptm/ptm_gets.cpp
+++ b/src/core/hle/service/ptm/ptm_gets.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ptm/ptm_gets.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PTM::PTM_Gets)
+
 namespace Service::PTM {
 
 PTM_Gets::PTM_Gets(std::shared_ptr<Module> ptm)
diff --git a/src/core/hle/service/ptm/ptm_gets.h b/src/core/hle/service/ptm/ptm_gets.h
index a8d71fdc9..57b8e5f55 100644
--- a/src/core/hle/service/ptm/ptm_gets.h
+++ b/src/core/hle/service/ptm/ptm_gets.h
@@ -12,6 +12,12 @@ namespace Service::PTM {
 class PTM_Gets final : public Module::Interface {
 public:
     explicit PTM_Gets(std::shared_ptr<Module> ptm);
+
+private:
+    SERVICE_SERIALIZATION(PTM_Gets, ptm, Module)
 };
 
 } // namespace Service::PTM
+
+BOOST_CLASS_EXPORT_KEY(Service::PTM::PTM_Gets)
+BOOST_SERIALIZATION_CONSTRUCT(Service::PTM::PTM_Gets)
diff --git a/src/core/hle/service/ptm/ptm_play.cpp b/src/core/hle/service/ptm/ptm_play.cpp
index 6ef45780e..00585ccce 100644
--- a/src/core/hle/service/ptm/ptm_play.cpp
+++ b/src/core/hle/service/ptm/ptm_play.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ptm/ptm_play.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PTM::PTM_Play)
+
 namespace Service::PTM {
 
 PTM_Play::PTM_Play(std::shared_ptr<Module> ptm)
diff --git a/src/core/hle/service/ptm/ptm_play.h b/src/core/hle/service/ptm/ptm_play.h
index 3a226149d..091e91d30 100644
--- a/src/core/hle/service/ptm/ptm_play.h
+++ b/src/core/hle/service/ptm/ptm_play.h
@@ -12,6 +12,12 @@ namespace Service::PTM {
 class PTM_Play final : public Module::Interface {
 public:
     explicit PTM_Play(std::shared_ptr<Module> ptm);
+
+private:
+    SERVICE_SERIALIZATION(PTM_Play, ptm, Module)
 };
 
 } // namespace Service::PTM
+
+BOOST_CLASS_EXPORT_KEY(Service::PTM::PTM_Play)
+BOOST_SERIALIZATION_CONSTRUCT(Service::PTM::PTM_Play)
diff --git a/src/core/hle/service/ptm/ptm_sets.cpp b/src/core/hle/service/ptm/ptm_sets.cpp
index b925f49c9..e0f436ddc 100644
--- a/src/core/hle/service/ptm/ptm_sets.cpp
+++ b/src/core/hle/service/ptm/ptm_sets.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ptm/ptm_sets.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PTM::PTM_Sets)
+
 namespace Service::PTM {
 
 PTM_Sets::PTM_Sets(std::shared_ptr<Module> ptm) : Module::Interface(std::move(ptm), "ptm:sets", 1) {
diff --git a/src/core/hle/service/ptm/ptm_sets.h b/src/core/hle/service/ptm/ptm_sets.h
index 317781faf..573b20dbd 100644
--- a/src/core/hle/service/ptm/ptm_sets.h
+++ b/src/core/hle/service/ptm/ptm_sets.h
@@ -12,6 +12,12 @@ namespace Service::PTM {
 class PTM_Sets final : public Module::Interface {
 public:
     explicit PTM_Sets(std::shared_ptr<Module> ptm);
+
+private:
+    SERVICE_SERIALIZATION(PTM_Sets, ptm, Module)
 };
 
 } // namespace Service::PTM
+
+BOOST_CLASS_EXPORT_KEY(Service::PTM::PTM_Sets)
+BOOST_SERIALIZATION_CONSTRUCT(Service::PTM::PTM_Sets)
diff --git a/src/core/hle/service/ptm/ptm_sysm.cpp b/src/core/hle/service/ptm/ptm_sysm.cpp
index 45ee1b6fc..48b44104e 100644
--- a/src/core/hle/service/ptm/ptm_sysm.cpp
+++ b/src/core/hle/service/ptm/ptm_sysm.cpp
@@ -2,8 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ptm/ptm_sysm.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PTM::PTM_S)
+SERIALIZE_EXPORT_IMPL(Service::PTM::PTM_Sysm)
+
 namespace Service::PTM {
 
 PTM_S_Common::PTM_S_Common(std::shared_ptr<Module> ptm, const char* name)
diff --git a/src/core/hle/service/ptm/ptm_sysm.h b/src/core/hle/service/ptm/ptm_sysm.h
index 8667f2a95..1e01fdb2e 100644
--- a/src/core/hle/service/ptm/ptm_sysm.h
+++ b/src/core/hle/service/ptm/ptm_sysm.h
@@ -17,11 +17,22 @@ public:
 class PTM_S final : public PTM_S_Common {
 public:
     explicit PTM_S(std::shared_ptr<Module> ptm);
+
+private:
+    SERVICE_SERIALIZATION(PTM_S, ptm, Module)
 };
 
 class PTM_Sysm final : public PTM_S_Common {
 public:
     explicit PTM_Sysm(std::shared_ptr<Module> ptm);
+
+private:
+    SERVICE_SERIALIZATION(PTM_Sysm, ptm, Module)
 };
 
 } // namespace Service::PTM
+
+BOOST_CLASS_EXPORT_KEY(Service::PTM::PTM_S)
+BOOST_CLASS_EXPORT_KEY(Service::PTM::PTM_Sysm)
+BOOST_SERIALIZATION_CONSTRUCT(Service::PTM::PTM_S)
+BOOST_SERIALIZATION_CONSTRUCT(Service::PTM::PTM_Sysm)
diff --git a/src/core/hle/service/ptm/ptm_u.cpp b/src/core/hle/service/ptm/ptm_u.cpp
index 647ef5961..4c1820df4 100644
--- a/src/core/hle/service/ptm/ptm_u.cpp
+++ b/src/core/hle/service/ptm/ptm_u.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/ptm/ptm_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PTM::PTM_U)
+
 namespace Service::PTM {
 
 PTM_U::PTM_U(std::shared_ptr<Module> ptm) : Module::Interface(std::move(ptm), "ptm:u", 26) {
diff --git a/src/core/hle/service/ptm/ptm_u.h b/src/core/hle/service/ptm/ptm_u.h
index 618401cec..213972242 100644
--- a/src/core/hle/service/ptm/ptm_u.h
+++ b/src/core/hle/service/ptm/ptm_u.h
@@ -12,6 +12,12 @@ namespace Service::PTM {
 class PTM_U final : public Module::Interface {
 public:
     explicit PTM_U(std::shared_ptr<Module> ptm);
+
+private:
+    SERVICE_SERIALIZATION(PTM_U, ptm, Module)
 };
 
 } // namespace Service::PTM
+
+BOOST_CLASS_EXPORT_KEY(Service::PTM::PTM_U)
+BOOST_SERIALIZATION_CONSTRUCT(Service::PTM::PTM_U)
diff --git a/src/core/hle/service/pxi/dev.cpp b/src/core/hle/service/pxi/dev.cpp
index dcea938a6..113551690 100644
--- a/src/core/hle/service/pxi/dev.cpp
+++ b/src/core/hle/service/pxi/dev.cpp
@@ -2,8 +2,11 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/service/pxi/dev.h"
 
+SERIALIZE_EXPORT_IMPL(Service::PXI::DEV)
+
 namespace Service::PXI {
 
 DEV::DEV() : ServiceFramework("pxi:dev", 1) {
diff --git a/src/core/hle/service/pxi/dev.h b/src/core/hle/service/pxi/dev.h
index 115dc2308..f16491077 100644
--- a/src/core/hle/service/pxi/dev.h
+++ b/src/core/hle/service/pxi/dev.h
@@ -13,6 +13,11 @@ class DEV final : public ServiceFramework<DEV> {
 public:
     DEV();
     ~DEV();
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::PXI
+
+BOOST_CLASS_EXPORT_KEY(Service::PXI::DEV)
diff --git a/src/core/hle/service/qtm/qtm_c.cpp b/src/core/hle/service/qtm/qtm_c.cpp
index 84baaba3a..2adc896df 100644
--- a/src/core/hle/service/qtm/qtm_c.cpp
+++ b/src/core/hle/service/qtm/qtm_c.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/qtm/qtm_c.h"
 
+SERIALIZE_EXPORT_IMPL(Service::QTM::QTM_C)
+
 namespace Service::QTM {
 
 QTM_C::QTM_C() : ServiceFramework("qtm:c", 2) {
diff --git a/src/core/hle/service/qtm/qtm_c.h b/src/core/hle/service/qtm/qtm_c.h
index c9cad0329..b601cd9de 100644
--- a/src/core/hle/service/qtm/qtm_c.h
+++ b/src/core/hle/service/qtm/qtm_c.h
@@ -12,6 +12,11 @@ class QTM_C final : public ServiceFramework<QTM_C> {
 public:
     QTM_C();
     ~QTM_C() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::QTM
+
+BOOST_CLASS_EXPORT_KEY(Service::QTM::QTM_C)
diff --git a/src/core/hle/service/qtm/qtm_s.cpp b/src/core/hle/service/qtm/qtm_s.cpp
index 2af7ced7b..6163ef4dc 100644
--- a/src/core/hle/service/qtm/qtm_s.cpp
+++ b/src/core/hle/service/qtm/qtm_s.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/qtm/qtm_s.h"
 
+SERIALIZE_EXPORT_IMPL(Service::QTM::QTM_S)
+
 namespace Service::QTM {
 
 QTM_S::QTM_S() : ServiceFramework("qtm:s", 2) {
diff --git a/src/core/hle/service/qtm/qtm_s.h b/src/core/hle/service/qtm/qtm_s.h
index 72b5e058b..b32a497db 100644
--- a/src/core/hle/service/qtm/qtm_s.h
+++ b/src/core/hle/service/qtm/qtm_s.h
@@ -12,6 +12,11 @@ class QTM_S final : public ServiceFramework<QTM_S> {
 public:
     QTM_S();
     ~QTM_S() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::QTM
+
+BOOST_CLASS_EXPORT_KEY(Service::QTM::QTM_S)
diff --git a/src/core/hle/service/qtm/qtm_sp.cpp b/src/core/hle/service/qtm/qtm_sp.cpp
index bd5a71605..fdfc80003 100644
--- a/src/core/hle/service/qtm/qtm_sp.cpp
+++ b/src/core/hle/service/qtm/qtm_sp.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/qtm/qtm_sp.h"
 
+SERIALIZE_EXPORT_IMPL(Service::QTM::QTM_SP)
+
 namespace Service::QTM {
 
 QTM_SP::QTM_SP() : ServiceFramework("qtm:sp", 2) {
diff --git a/src/core/hle/service/qtm/qtm_sp.h b/src/core/hle/service/qtm/qtm_sp.h
index c3f1049a1..2f290f192 100644
--- a/src/core/hle/service/qtm/qtm_sp.h
+++ b/src/core/hle/service/qtm/qtm_sp.h
@@ -12,6 +12,11 @@ class QTM_SP final : public ServiceFramework<QTM_SP> {
 public:
     QTM_SP();
     ~QTM_SP() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::QTM
+
+BOOST_CLASS_EXPORT_KEY(Service::QTM::QTM_SP)
diff --git a/src/core/hle/service/qtm/qtm_u.cpp b/src/core/hle/service/qtm/qtm_u.cpp
index 471692189..84415dde9 100644
--- a/src/core/hle/service/qtm/qtm_u.cpp
+++ b/src/core/hle/service/qtm/qtm_u.cpp
@@ -2,9 +2,12 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/qtm/qtm_u.h"
 
+SERIALIZE_EXPORT_IMPL(Service::QTM::QTM_U)
+
 namespace Service::QTM {
 
 QTM_U::QTM_U() : ServiceFramework("qtm:u", 2) {
diff --git a/src/core/hle/service/qtm/qtm_u.h b/src/core/hle/service/qtm/qtm_u.h
index 01bb1e6e0..d1aee1e97 100644
--- a/src/core/hle/service/qtm/qtm_u.h
+++ b/src/core/hle/service/qtm/qtm_u.h
@@ -12,6 +12,11 @@ class QTM_U final : public ServiceFramework<QTM_U> {
 public:
     QTM_U();
     ~QTM_U() = default;
+
+private:
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 } // namespace Service::QTM
+
+BOOST_CLASS_EXPORT_KEY(Service::QTM::QTM_U)
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index db6a0ad23..83b0a5fb1 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -10,7 +10,10 @@
 #include <memory>
 #include <string>
 #include <boost/container/flat_map.hpp>
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/shared_ptr.hpp>
 #include "common/common_types.h"
+#include "common/construct.h"
 #include "core/hle/kernel/hle_ipc.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/service/sm/sm.h"
@@ -85,6 +88,7 @@ private:
     using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member,
                            Kernel::HLERequestContext& ctx);
 
+    // TODO: Replace all these with virtual functions!
     ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker);
     ~ServiceFrameworkBase() override;
 
@@ -193,3 +197,45 @@ struct ServiceModuleInfo {
 extern const std::array<ServiceModuleInfo, 40> service_module_map;
 
 } // namespace Service
+
+#define SERVICE_SERIALIZATION(T, MFIELD, TMODULE)                                                  \
+    template <class Archive>                                                                       \
+    void save_construct(Archive& ar, const unsigned int file_version) const {                      \
+        ar << MFIELD;                                                                              \
+    }                                                                                              \
+                                                                                                   \
+    template <class Archive>                                                                       \
+    static void load_construct(Archive& ar, T* t, const unsigned int file_version) {               \
+        std::shared_ptr<TMODULE> MFIELD;                                                           \
+        ar >> MFIELD;                                                                              \
+        ::new (t) T(MFIELD);                                                                       \
+    }                                                                                              \
+                                                                                                   \
+    template <class Archive>                                                                       \
+    void serialize(Archive& ar, const unsigned int) {                                              \
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);               \
+    }                                                                                              \
+    friend class boost::serialization::access;                                                     \
+    friend class ::construct_access;
+
+#define SERVICE_SERIALIZATION_SIMPLE                                                               \
+    template <class Archive>                                                                       \
+    void serialize(Archive& ar, const unsigned int) {                                              \
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);               \
+    }                                                                                              \
+    friend class boost::serialization::access;
+
+#define SERVICE_CONSTRUCT(T)                                                                       \
+    namespace boost::serialization {                                                               \
+    template <class Archive>                                                                       \
+    void load_construct_data(Archive& ar, T* t, const unsigned int);                               \
+    }
+
+#define SERVICE_CONSTRUCT_IMPL(T)                                                                  \
+    namespace boost::serialization {                                                               \
+    template <class Archive>                                                                       \
+    void load_construct_data(Archive& ar, T* t, const unsigned int) {                              \
+        ::new (t) T(Core::Global<Core::System>());                                                 \
+    }                                                                                              \
+    template void load_construct_data<iarchive>(iarchive & ar, T* t, const unsigned int);          \
+    }
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 6e47fd152..659e14afb 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -8,6 +8,10 @@
 #include <string>
 #include <type_traits>
 #include <unordered_map>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/split_member.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/unordered_map.hpp>
 #include "core/hle/kernel/client_port.h"
 #include "core/hle/kernel/object.h"
 #include "core/hle/kernel/server_port.h"
@@ -80,6 +84,24 @@ private:
     // For IPC Recorder
     /// client port Object id -> service name
     std::unordered_map<u32, std::string> registered_services_inverse;
+
+    template <class Archive>
+    void save(Archive& ar, const unsigned int file_version) const {
+        ar << registered_services;
+    }
+
+    template <class Archive>
+    void load(Archive& ar, const unsigned int file_version) {
+        ar >> registered_services;
+        registered_services_inverse.clear();
+        for (const auto& pair : registered_services) {
+            registered_services_inverse.emplace(pair.second->GetObjectId(), pair.first);
+        }
+    }
+
+    BOOST_SERIALIZATION_SPLIT_MEMBER()
+
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::SM
diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp
index 396bd3559..da6d57df7 100644
--- a/src/core/hle/service/sm/srv.cpp
+++ b/src/core/hle/service/sm/srv.cpp
@@ -3,6 +3,10 @@
 // Refer to the license.txt file included.
 
 #include <tuple>
+#include <boost/serialization/shared_ptr.hpp>
+#include <boost/serialization/string.hpp>
+#include <boost/serialization/unordered_map.hpp>
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -20,8 +24,18 @@
 #include "core/hle/service/sm/sm.h"
 #include "core/hle/service/sm/srv.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::SM::SRV)
+SERIALIZE_EXPORT_IMPL(Service::SM::SRV)
+
 namespace Service::SM {
 
+template <class Archive>
+void SRV::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar& notification_semaphore;
+    ar& get_service_handle_delayed_map;
+}
+
 constexpr int MAX_PENDING_NOTIFICATIONS = 16;
 
 /**
@@ -71,6 +85,48 @@ void SRV::EnableNotification(Kernel::HLERequestContext& ctx) {
     LOG_WARNING(Service_SRV, "(STUBBED) called");
 }
 
+class SRV::ThreadCallback : public Kernel::HLERequestContext::WakeupCallback {
+
+public:
+    explicit ThreadCallback(Core::System& system_, std::string name_)
+        : system(system_), name(name_) {}
+
+    void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
+                Kernel::ThreadWakeupReason reason) {
+        LOG_ERROR(Service_SRV, "called service={} wakeup", name);
+        auto client_port = system.ServiceManager().GetServicePort(name);
+
+        auto session = client_port.Unwrap()->Connect();
+        if (session.Succeeded()) {
+            LOG_DEBUG(Service_SRV, "called service={} -> session={}", name,
+                      (*session)->GetObjectId());
+            IPC::RequestBuilder rb(ctx, 0x5, 1, 2);
+            rb.Push(session.Code());
+            rb.PushMoveObjects(std::move(session).Unwrap());
+        } else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED) {
+            LOG_ERROR(Service_SRV, "called service={} -> ERR_MAX_CONNECTIONS_REACHED", name);
+            UNREACHABLE();
+        } else {
+            LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, session.Code().raw);
+            IPC::RequestBuilder rb(ctx, 0x5, 1, 0);
+            rb.Push(session.Code());
+        }
+    }
+
+private:
+    Core::System& system;
+    std::string name;
+
+    ThreadCallback() : system(Core::Global<Core::System>()) {}
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::HLERequestContext::WakeupCallback>(*this);
+        ar& name;
+    }
+    friend class boost::serialization::access;
+};
+
 /**
  * SRV::GetServiceHandle service function
  *  Inputs:
@@ -100,28 +156,7 @@ void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) {
 
     // TODO(yuriks): Permission checks go here
 
-    auto get_handle = [name, this](std::shared_ptr<Kernel::Thread> thread,
-                                   Kernel::HLERequestContext& ctx,
-                                   Kernel::ThreadWakeupReason reason) {
-        LOG_ERROR(Service_SRV, "called service={} wakeup", name);
-        auto client_port = system.ServiceManager().GetServicePort(name);
-
-        auto session = client_port.Unwrap()->Connect();
-        if (session.Succeeded()) {
-            LOG_DEBUG(Service_SRV, "called service={} -> session={}", name,
-                      (*session)->GetObjectId());
-            IPC::RequestBuilder rb(ctx, 0x5, 1, 2);
-            rb.Push(session.Code());
-            rb.PushMoveObjects(std::move(session).Unwrap());
-        } else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED) {
-            LOG_ERROR(Service_SRV, "called service={} -> ERR_MAX_CONNECTIONS_REACHED", name);
-            UNREACHABLE();
-        } else {
-            LOG_ERROR(Service_SRV, "called service={} -> error 0x{:08X}", name, session.Code().raw);
-            IPC::RequestBuilder rb(ctx, 0x5, 1, 0);
-            rb.Push(session.Code());
-        }
-    };
+    auto get_handle = std::make_shared<ThreadCallback>(system, name);
 
     auto client_port = system.ServiceManager().GetServicePort(name);
     if (client_port.Failed()) {
@@ -266,3 +301,5 @@ SRV::SRV(Core::System& system) : ServiceFramework("srv:", 4), system(system) {
 SRV::~SRV() = default;
 
 } // namespace Service::SM
+
+SERIALIZE_EXPORT_IMPL(Service::SM::SRV::ThreadCallback)
diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h
index 2382f4842..753218dca 100644
--- a/src/core/hle/service/sm/srv.h
+++ b/src/core/hle/service/sm/srv.h
@@ -6,6 +6,7 @@
 
 #include <memory>
 #include <unordered_map>
+#include <boost/serialization/export.hpp>
 #include "core/hle/service/service.h"
 
 namespace Core {
@@ -25,6 +26,8 @@ public:
     explicit SRV(Core::System& system);
     ~SRV();
 
+    class ThreadCallback;
+
 private:
     void RegisterClient(Kernel::HLERequestContext& ctx);
     void EnableNotification(Kernel::HLERequestContext& ctx);
@@ -37,6 +40,14 @@ private:
     Core::System& system;
     std::shared_ptr<Kernel::Semaphore> notification_semaphore;
     std::unordered_map<std::string, std::shared_ptr<Kernel::Event>> get_service_handle_delayed_map;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 } // namespace Service::SM
+
+SERVICE_CONSTRUCT(Service::SM::SRV)
+BOOST_CLASS_EXPORT_KEY(Service::SM::SRV)
+BOOST_CLASS_EXPORT_KEY(Service::SM::SRV::ThreadCallback)
diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp
index 5ebe55356..56b48cd3a 100644
--- a/src/core/hle/service/soc_u.cpp
+++ b/src/core/hle/service/soc_u.cpp
@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <cstring>
 #include <vector>
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/bit_field.h"
 #include "common/common_types.h"
@@ -52,6 +53,8 @@
 #define closesocket(x) close(x)
 #endif
 
+SERIALIZE_EXPORT_IMPL(Service::SOC::SOC_U)
+
 namespace Service::SOC {
 
 const s32 SOCKET_ERROR_VALUE = -1;
diff --git a/src/core/hle/service/soc_u.h b/src/core/hle/service/soc_u.h
index 273aac49c..595a984f2 100644
--- a/src/core/hle/service/soc_u.h
+++ b/src/core/hle/service/soc_u.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <unordered_map>
+#include <boost/serialization/unordered_map.hpp>
 #include "core/hle/service/service.h"
 
 namespace Core {
@@ -17,6 +18,14 @@ namespace Service::SOC {
 struct SocketHolder {
     u32 socket_fd; ///< The socket descriptor
     bool blocking; ///< Whether the socket is blocking or not, it is only read on Windows.
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& socket_fd;
+        ar& blocking;
+    }
+    friend class boost::serialization::access;
 };
 
 class SOC_U final : public ServiceFramework<SOC_U> {
@@ -55,8 +64,17 @@ private:
 
     /// Holds info about the currently open sockets
     std::unordered_map<u32, SocketHolder> open_sockets;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+        ar& open_sockets;
+    }
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::SOC
+
+BOOST_CLASS_EXPORT_KEY(Service::SOC::SOC_U)
diff --git a/src/core/hle/service/ssl_c.cpp b/src/core/hle/service/ssl_c.cpp
index b862c41f1..8e74c1b7c 100644
--- a/src/core/hle/service/ssl_c.cpp
+++ b/src/core/hle/service/ssl_c.cpp
@@ -2,12 +2,14 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include "common/archives.h"
 #include "common/common_types.h"
 #include "core/core.h"
 #include "core/hle/ipc.h"
 #include "core/hle/ipc_helpers.h"
 #include "core/hle/service/ssl_c.h"
 
+SERIALIZE_EXPORT_IMPL(Service::SSL::SSL_C)
 namespace Service::SSL {
 
 void SSL_C::Initialize(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/ssl_c.h b/src/core/hle/service/ssl_c.h
index 807f7c725..30b87378a 100644
--- a/src/core/hle/service/ssl_c.h
+++ b/src/core/hle/service/ssl_c.h
@@ -23,8 +23,12 @@ private:
 
     // TODO: Implement a proper CSPRNG in the future when actual security is needed
     std::mt19937 rand_gen;
+
+    SERVICE_SERIALIZATION_SIMPLE
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::SSL
+
+BOOST_CLASS_EXPORT_KEY(Service::SSL::SSL_C)
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index 356ebd843..89da8058c 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <cstring>
+#include "common/archives.h"
 #include "common/common_funcs.h"
 #include "common/logging/log.h"
 #include "core/core.h"
@@ -12,8 +13,22 @@
 #include "core/hle/service/y2r_u.h"
 #include "core/hw/y2r.h"
 
+SERVICE_CONSTRUCT_IMPL(Service::Y2R::Y2R_U)
+SERIALIZE_EXPORT_IMPL(Service::Y2R::Y2R_U)
+
 namespace Service::Y2R {
 
+template <class Archive>
+void Y2R_U::serialize(Archive& ar, const unsigned int) {
+    ar& boost::serialization::base_object<Kernel::SessionRequestHandler>(*this);
+    ar& completion_event;
+    ar& conversion;
+    ar& dithering_weight_params;
+    ar& temporal_dithering_enabled;
+    ar& transfer_end_interrupt_enabled;
+    ar& spacial_dithering_enabled;
+}
+
 static const CoefficientSet standard_coefficients[4] = {
     {{0x100, 0x166, 0xB6, 0x58, 0x1C5, -0x166F, 0x10EE, -0x1C5B}}, // ITU_Rec601
     {{0x100, 0x193, 0x77, 0x2F, 0x1DB, -0x1933, 0xA7C, -0x1D51}},  // ITU_Rec709
diff --git a/src/core/hle/service/y2r_u.h b/src/core/hle/service/y2r_u.h
index 332d3b240..1ac675f92 100644
--- a/src/core/hle/service/y2r_u.h
+++ b/src/core/hle/service/y2r_u.h
@@ -7,6 +7,7 @@
 #include <array>
 #include <memory>
 #include <string>
+#include <boost/serialization/array.hpp>
 #include "common/common_types.h"
 #include "core/hle/result.h"
 #include "core/hle/service/service.h"
@@ -91,6 +92,16 @@ struct ConversionBuffer {
     u16 transfer_unit;
     /// Amount of bytes to be skipped between copying each `transfer_unit` bytes.
     u16 gap;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& address;
+        ar& image_size;
+        ar& transfer_unit;
+        ar& gap;
+    }
+    friend class boost::serialization::access;
 };
 
 struct ConversionConfiguration {
@@ -112,6 +123,26 @@ struct ConversionConfiguration {
     ResultCode SetInputLineWidth(u16 width);
     ResultCode SetInputLines(u16 lines);
     ResultCode SetStandardCoefficient(StandardCoefficient standard_coefficient);
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& input_format;
+        ar& output_format;
+        ar& rotation;
+        ar& block_alignment;
+        ar& input_line_width;
+        ar& input_lines;
+        ar& coefficients;
+        ar& padding;
+        ar& alpha;
+        ar& src_Y;
+        ar& src_U;
+        ar& src_V;
+        ar& src_YUYV;
+        ar& dst;
+    }
+    friend class boost::serialization::access;
 };
 
 struct DitheringWeightParams {
@@ -131,6 +162,28 @@ struct DitheringWeightParams {
     u16 w3_xOdd_yEven;
     u16 w3_xEven_yOdd;
     u16 w3_xOdd_yOdd;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& w0_xEven_yEven;
+        ar& w0_xOdd_yEven;
+        ar& w0_xEven_yOdd;
+        ar& w0_xOdd_yOdd;
+        ar& w1_xEven_yEven;
+        ar& w1_xOdd_yEven;
+        ar& w1_xEven_yOdd;
+        ar& w1_xOdd_yOdd;
+        ar& w2_xEven_yEven;
+        ar& w2_xOdd_yEven;
+        ar& w2_xEven_yOdd;
+        ar& w2_xOdd_yOdd;
+        ar& w3_xEven_yEven;
+        ar& w3_xOdd_yEven;
+        ar& w3_xEven_yOdd;
+        ar& w3_xOdd_yOdd;
+    }
+    friend class boost::serialization::access;
 };
 
 struct ConversionParameters {
@@ -301,8 +354,15 @@ private:
     bool temporal_dithering_enabled = false;
     bool transfer_end_interrupt_enabled = false;
     bool spacial_dithering_enabled = false;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int);
+    friend class boost::serialization::access;
 };
 
 void InstallInterfaces(Core::System& system);
 
 } // namespace Service::Y2R
+
+SERVICE_CONSTRUCT(Service::Y2R::Y2R_U)
+BOOST_CLASS_EXPORT_KEY(Service::Y2R::Y2R_U)
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index f641afae4..4f6a31441 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -472,14 +472,7 @@ inline void Write(u32 addr, const T data) {
         if (config.trigger & 1) {
             MICROPROFILE_SCOPE(GPU_CmdlistProcessing);
 
-            u32* buffer = (u32*)g_memory->GetPhysicalPointer(config.GetPhysicalAddress());
-
-            if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
-                Pica::g_debug_context->recorder->MemoryAccessed((u8*)buffer, config.size,
-                                                                config.GetPhysicalAddress());
-            }
-
-            Pica::CommandProcessor::ProcessCommandList(buffer, config.size);
+            Pica::CommandProcessor::ProcessCommandList(config.GetPhysicalAddress(), config.size);
 
             g_regs.command_processor_config.trigger = 0;
         }
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index ac30bc22e..3252364e2 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -6,6 +6,8 @@
 
 #include <cstddef>
 #include <type_traits>
+#include <boost/serialization/access.hpp>
+#include <boost/serialization/binary_object.hpp>
 #include "common/assert.h"
 #include "common/bit_field.h"
 #include "common/common_funcs.h"
@@ -270,6 +272,12 @@ private:
     static inline u32 DecodeAddressRegister(u32 register_value) {
         return register_value * 8;
     }
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::make_binary_object(this, sizeof(Regs));
+    }
+    friend class boost::serialization::access;
 };
 static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout");
 
diff --git a/src/core/hw/lcd.h b/src/core/hw/lcd.h
index 5e37121f7..9667c7c4c 100644
--- a/src/core/hw/lcd.h
+++ b/src/core/hw/lcd.h
@@ -6,6 +6,7 @@
 
 #include <cstddef>
 #include <type_traits>
+#include <boost/serialization/access.hpp>
 #include "common/bit_field.h"
 #include "common/common_funcs.h"
 #include "common/common_types.h"
@@ -50,6 +51,16 @@ struct Regs {
         u32* content = reinterpret_cast<u32*>(this);
         return content[index];
     }
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& color_fill_top.raw;
+        ar& backlight_top;
+        ar& color_fill_bottom.raw;
+        ar& backlight_bottom;
+    }
+    friend class boost::serialization::access;
 };
 static_assert(std::is_standard_layout<Regs>::value, "Structure does not use standard layout");
 
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 096f4c697..c6a213ff0 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -4,22 +4,38 @@
 
 #include <array>
 #include <cstring>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/binary_object.hpp>
 #include "audio_core/dsp_interface.h"
+#include "common/archives.h"
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/logging/log.h"
 #include "common/swap.h"
 #include "core/arm/arm_interface.h"
 #include "core/core.h"
+#include "core/global.h"
 #include "core/hle/kernel/memory.h"
 #include "core/hle/kernel/process.h"
 #include "core/hle/lock.h"
 #include "core/memory.h"
+#include "core/settings.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 
+SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::FCRAM>)
+SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::VRAM>)
+SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::DSP>)
+SERIALIZE_EXPORT_IMPL(Memory::MemorySystem::BackingMemImpl<Memory::Region::N3DS>)
+
 namespace Memory {
 
+void PageTable::Clear() {
+    pointers.raw.fill(nullptr);
+    pointers.refs.fill(MemoryRef());
+    attributes.fill(PageType::Unmapped);
+}
+
 class RasterizerCacheMarker {
 public:
     void Mark(VAddr addr, bool cached) {
@@ -52,6 +68,15 @@ private:
     std::array<bool, VRAM_SIZE / PAGE_SIZE> vram{};
     std::array<bool, LINEAR_HEAP_SIZE / PAGE_SIZE> linear_heap{};
     std::array<bool, NEW_LINEAR_HEAP_SIZE / PAGE_SIZE> new_linear_heap{};
+
+    static_assert(sizeof(bool) == 1);
+    friend class boost::serialization::access;
+    template <typename Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& vram;
+        ar& linear_heap;
+        ar& new_linear_heap;
+    }
 };
 
 class MemorySystem::Impl {
@@ -62,26 +87,140 @@ public:
     std::unique_ptr<u8[]> vram = std::make_unique<u8[]>(Memory::VRAM_SIZE);
     std::unique_ptr<u8[]> n3ds_extra_ram = std::make_unique<u8[]>(Memory::N3DS_EXTRA_RAM_SIZE);
 
-    PageTable* current_page_table = nullptr;
+    std::shared_ptr<PageTable> current_page_table = nullptr;
     RasterizerCacheMarker cache_marker;
-    std::vector<PageTable*> page_table_list;
+    std::vector<std::shared_ptr<PageTable>> page_table_list;
 
     AudioCore::DspInterface* dsp = nullptr;
+
+    std::shared_ptr<BackingMem> fcram_mem;
+    std::shared_ptr<BackingMem> vram_mem;
+    std::shared_ptr<BackingMem> n3ds_extra_ram_mem;
+    std::shared_ptr<BackingMem> dsp_mem;
+
+    Impl();
+
+    const u8* GetPtr(Region r) const {
+        switch (r) {
+        case Region::VRAM:
+            return vram.get();
+        case Region::DSP:
+            return dsp->GetDspMemory().data();
+        case Region::FCRAM:
+            return fcram.get();
+        case Region::N3DS:
+            return n3ds_extra_ram.get();
+        default:
+            UNREACHABLE();
+        }
+    }
+
+    u8* GetPtr(Region r) {
+        switch (r) {
+        case Region::VRAM:
+            return vram.get();
+        case Region::DSP:
+            return dsp->GetDspMemory().data();
+        case Region::FCRAM:
+            return fcram.get();
+        case Region::N3DS:
+            return n3ds_extra_ram.get();
+        default:
+            UNREACHABLE();
+        }
+    }
+
+    u32 GetSize(Region r) const {
+        switch (r) {
+        case Region::VRAM:
+            return VRAM_SIZE;
+        case Region::DSP:
+            return DSP_RAM_SIZE;
+        case Region::FCRAM:
+            return FCRAM_N3DS_SIZE;
+        case Region::N3DS:
+            return N3DS_EXTRA_RAM_SIZE;
+        default:
+            UNREACHABLE();
+        }
+    }
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        bool save_n3ds_ram = Settings::values.is_new_3ds;
+        ar& save_n3ds_ram;
+        ar& boost::serialization::make_binary_object(vram.get(), Memory::VRAM_SIZE);
+        ar& boost::serialization::make_binary_object(
+            fcram.get(), save_n3ds_ram ? Memory::FCRAM_N3DS_SIZE : Memory::FCRAM_SIZE);
+        ar& boost::serialization::make_binary_object(
+            n3ds_extra_ram.get(), save_n3ds_ram ? Memory::N3DS_EXTRA_RAM_SIZE : 0);
+        ar& cache_marker;
+        ar& page_table_list;
+        // dsp is set from Core::System at startup
+        ar& current_page_table;
+        ar& fcram_mem;
+        ar& vram_mem;
+        ar& n3ds_extra_ram_mem;
+        ar& dsp_mem;
+    }
 };
 
+// We use this rather than BufferMem because we don't want new objects to be allocated when
+// deserializing. This avoids unnecessary memory thrashing.
+template <Region R>
+class MemorySystem::BackingMemImpl : public BackingMem {
+public:
+    BackingMemImpl() : impl(*Core::Global<Core::System>().Memory().impl) {}
+    explicit BackingMemImpl(MemorySystem::Impl& impl_) : impl(impl_) {}
+    u8* GetPtr() override {
+        return impl.GetPtr(R);
+    }
+    const u8* GetPtr() const override {
+        return impl.GetPtr(R);
+    }
+    std::size_t GetSize() const override {
+        return impl.GetSize(R);
+    }
+
+private:
+    MemorySystem::Impl& impl;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::base_object<BackingMem>(*this);
+    }
+    friend class boost::serialization::access;
+};
+
+MemorySystem::Impl::Impl()
+    : fcram_mem(std::make_shared<BackingMemImpl<Region::FCRAM>>(*this)),
+      vram_mem(std::make_shared<BackingMemImpl<Region::VRAM>>(*this)),
+      n3ds_extra_ram_mem(std::make_shared<BackingMemImpl<Region::N3DS>>(*this)),
+      dsp_mem(std::make_shared<BackingMemImpl<Region::DSP>>(*this)) {}
+
 MemorySystem::MemorySystem() : impl(std::make_unique<Impl>()) {}
 MemorySystem::~MemorySystem() = default;
 
-void MemorySystem::SetCurrentPageTable(PageTable* page_table) {
+template <class Archive>
+void MemorySystem::serialize(Archive& ar, const unsigned int file_version) {
+    ar&* impl.get();
+}
+
+SERIALIZE_IMPL(MemorySystem)
+
+void MemorySystem::SetCurrentPageTable(std::shared_ptr<PageTable> page_table) {
     impl->current_page_table = page_table;
 }
 
-PageTable* MemorySystem::GetCurrentPageTable() const {
+std::shared_ptr<PageTable> MemorySystem::GetCurrentPageTable() const {
     return impl->current_page_table;
 }
 
-void MemorySystem::MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type) {
-    LOG_DEBUG(HW_Memory, "Mapping {} onto {:08X}-{:08X}", (void*)memory, base * PAGE_SIZE,
+void MemorySystem::MapPages(PageTable& page_table, u32 base, u32 size, MemoryRef memory,
+                            PageType type) {
+    LOG_DEBUG(HW_Memory, "Mapping {} onto {:08X}-{:08X}", (void*)memory.GetPtr(), base * PAGE_SIZE,
               (base + size) * PAGE_SIZE);
 
     RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
@@ -101,12 +240,12 @@ void MemorySystem::MapPages(PageTable& page_table, u32 base, u32 size, u8* memor
         }
 
         base += 1;
-        if (memory != nullptr)
+        if (memory != nullptr && memory.GetSize() > PAGE_SIZE)
             memory += PAGE_SIZE;
     }
 }
 
-void MemorySystem::MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target) {
+void MemorySystem::MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, MemoryRef target) {
     ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:08X}", size);
     ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:08X}", base);
     MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
@@ -127,26 +266,28 @@ void MemorySystem::UnmapRegion(PageTable& page_table, VAddr base, u32 size) {
     MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
 }
 
-u8* MemorySystem::GetPointerForRasterizerCache(VAddr addr) {
+MemoryRef MemorySystem::GetPointerForRasterizerCache(VAddr addr) {
     if (addr >= LINEAR_HEAP_VADDR && addr < LINEAR_HEAP_VADDR_END) {
-        return impl->fcram.get() + (addr - LINEAR_HEAP_VADDR);
+        return {impl->fcram_mem, addr - LINEAR_HEAP_VADDR};
     }
     if (addr >= NEW_LINEAR_HEAP_VADDR && addr < NEW_LINEAR_HEAP_VADDR_END) {
-        return impl->fcram.get() + (addr - NEW_LINEAR_HEAP_VADDR);
+        return {impl->fcram_mem, addr - NEW_LINEAR_HEAP_VADDR};
     }
     if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) {
-        return impl->vram.get() + (addr - VRAM_VADDR);
+        return {impl->vram_mem, addr - VRAM_VADDR};
     }
     UNREACHABLE();
 }
 
-void MemorySystem::RegisterPageTable(PageTable* page_table) {
+void MemorySystem::RegisterPageTable(std::shared_ptr<PageTable> page_table) {
     impl->page_table_list.push_back(page_table);
 }
 
-void MemorySystem::UnregisterPageTable(PageTable* page_table) {
-    impl->page_table_list.erase(
-        std::find(impl->page_table_list.begin(), impl->page_table_list.end(), page_table));
+void MemorySystem::UnregisterPageTable(std::shared_ptr<PageTable> page_table) {
+    auto it = std::find(impl->page_table_list.begin(), impl->page_table_list.end(), page_table);
+    if (it != impl->page_table_list.end()) {
+        impl->page_table_list.erase(it);
+    }
 }
 
 /**
@@ -232,9 +373,9 @@ void MemorySystem::Write(const VAddr vaddr, const T data) {
 }
 
 bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
-    auto& page_table = process.vm_manager.page_table;
+    auto& page_table = *process.vm_manager.page_table;
 
-    const u8* page_pointer = page_table.pointers[vaddr >> PAGE_BITS];
+    auto page_pointer = page_table.pointers[vaddr >> PAGE_BITS];
     if (page_pointer)
         return true;
 
@@ -286,6 +427,10 @@ std::string MemorySystem::ReadCString(VAddr vaddr, std::size_t max_length) {
 }
 
 u8* MemorySystem::GetPhysicalPointer(PAddr address) {
+    return GetPhysicalRef(address);
+}
+
+MemoryRef MemorySystem::GetPhysicalRef(PAddr address) {
     struct MemoryArea {
         PAddr paddr_base;
         u32 size;
@@ -312,25 +457,28 @@ u8* MemorySystem::GetPhysicalPointer(PAddr address) {
 
     u32 offset_into_region = address - area->paddr_base;
 
-    u8* target_pointer = nullptr;
+    std::shared_ptr<BackingMem> target_mem = nullptr;
     switch (area->paddr_base) {
     case VRAM_PADDR:
-        target_pointer = impl->vram.get() + offset_into_region;
+        target_mem = impl->vram_mem;
         break;
     case DSP_RAM_PADDR:
-        target_pointer = impl->dsp->GetDspMemory().data() + offset_into_region;
+        target_mem = impl->dsp_mem;
         break;
     case FCRAM_PADDR:
-        target_pointer = impl->fcram.get() + offset_into_region;
+        target_mem = impl->fcram_mem;
         break;
     case N3DS_EXTRA_RAM_PADDR:
-        target_pointer = impl->n3ds_extra_ram.get() + offset_into_region;
+        target_mem = impl->n3ds_extra_ram_mem;
         break;
     default:
         UNREACHABLE();
     }
+    if (offset_into_region >= target_mem->GetSize()) {
+        return {nullptr};
+    }
 
-    return target_pointer;
+    return {target_mem, offset_into_region};
 }
 
 /// For a rasterizer-accessible PAddr, gets a list of all possible VAddr
@@ -363,7 +511,7 @@ void MemorySystem::RasterizerMarkRegionCached(PAddr start, u32 size, bool cached
     for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) {
         for (VAddr vaddr : PhysicalToVirtualAddressForRasterizer(paddr)) {
             impl->cache_marker.Mark(vaddr, cached);
-            for (PageTable* page_table : impl->page_table_list) {
+            for (auto page_table : impl->page_table_list) {
                 PageType& page_type = page_table->attributes[vaddr >> PAGE_BITS];
 
                 if (cached) {
@@ -428,6 +576,16 @@ void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) {
     VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size);
 }
 
+void RasterizerClearAll(bool flush) {
+    // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+    // null here
+    if (VideoCore::g_renderer == nullptr) {
+        return;
+    }
+
+    VideoCore::g_renderer->Rasterizer()->ClearAll(flush);
+}
+
 void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) {
     // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
     // null here
@@ -485,7 +643,7 @@ u64 MemorySystem::Read64(const VAddr addr) {
 
 void MemorySystem::ReadBlock(const Kernel::Process& process, const VAddr src_addr,
                              void* dest_buffer, const std::size_t size) {
-    auto& page_table = process.vm_manager.page_table;
+    auto& page_table = *process.vm_manager.page_table;
 
     std::size_t remaining_size = size;
     std::size_t page_index = src_addr >> PAGE_BITS;
@@ -551,7 +709,7 @@ void MemorySystem::Write64(const VAddr addr, const u64 data) {
 
 void MemorySystem::WriteBlock(const Kernel::Process& process, const VAddr dest_addr,
                               const void* src_buffer, const std::size_t size) {
-    auto& page_table = process.vm_manager.page_table;
+    auto& page_table = *process.vm_manager.page_table;
     std::size_t remaining_size = size;
     std::size_t page_index = dest_addr >> PAGE_BITS;
     std::size_t page_offset = dest_addr & PAGE_MASK;
@@ -599,7 +757,7 @@ void MemorySystem::WriteBlock(const Kernel::Process& process, const VAddr dest_a
 
 void MemorySystem::ZeroBlock(const Kernel::Process& process, const VAddr dest_addr,
                              const std::size_t size) {
-    auto& page_table = process.vm_manager.page_table;
+    auto& page_table = *process.vm_manager.page_table;
     std::size_t remaining_size = size;
     std::size_t page_index = dest_addr >> PAGE_BITS;
     std::size_t page_offset = dest_addr & PAGE_MASK;
@@ -654,7 +812,7 @@ void MemorySystem::CopyBlock(const Kernel::Process& process, VAddr dest_addr, VA
 void MemorySystem::CopyBlock(const Kernel::Process& dest_process,
                              const Kernel::Process& src_process, VAddr dest_addr, VAddr src_addr,
                              std::size_t size) {
-    auto& page_table = src_process.vm_manager.page_table;
+    auto& page_table = *src_process.vm_manager.page_table;
     std::size_t remaining_size = size;
     std::size_t page_index = src_addr >> PAGE_BITS;
     std::size_t page_offset = src_addr & PAGE_MASK;
@@ -744,9 +902,9 @@ void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data)
     mmio_handler->Write64(addr, data);
 }
 
-u32 MemorySystem::GetFCRAMOffset(u8* pointer) {
+u32 MemorySystem::GetFCRAMOffset(const u8* pointer) {
     ASSERT(pointer >= impl->fcram.get() && pointer <= impl->fcram.get() + Memory::FCRAM_N3DS_SIZE);
-    return pointer - impl->fcram.get();
+    return static_cast<u32>(pointer - impl->fcram.get());
 }
 
 u8* MemorySystem::GetFCRAMPointer(u32 offset) {
@@ -754,6 +912,11 @@ u8* MemorySystem::GetFCRAMPointer(u32 offset) {
     return impl->fcram.get() + offset;
 }
 
+MemoryRef MemorySystem::GetFCRAMRef(u32 offset) {
+    ASSERT(offset <= Memory::FCRAM_N3DS_SIZE);
+    return MemoryRef(impl->fcram_mem, offset);
+}
+
 void MemorySystem::SetDSP(AudioCore::DspInterface& dsp) {
     impl->dsp = &dsp;
 }
diff --git a/src/core/memory.h b/src/core/memory.h
index 6caca5a2b..66b28d87d 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -9,7 +9,10 @@
 #include <memory>
 #include <string>
 #include <vector>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
+#include "common/memory_ref.h"
 #include "core/mmio.h"
 
 class ARM_Interface;
@@ -52,6 +55,15 @@ struct SpecialRegion {
     VAddr base;
     u32 size;
     MMIORegionPointer handler;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& base;
+        ar& size;
+        ar& handler;
+    }
+    friend class boost::serialization::access;
 };
 
 /**
@@ -65,7 +77,48 @@ struct PageTable {
      * Array of memory pointers backing each page. An entry can only be non-null if the
      * corresponding entry in the `attributes` array is of type `Memory`.
      */
-    std::array<u8*, PAGE_TABLE_NUM_ENTRIES> pointers;
+
+    // The reason for this rigmarole is to keep the 'raw' and 'refs' arrays in sync.
+    // We need 'raw' for dynarmic and 'refs' for serialization
+    struct Pointers {
+
+        struct Entry {
+            Entry(Pointers& pointers_, VAddr idx_) : pointers(pointers_), idx(idx_) {}
+
+            void operator=(MemoryRef value) {
+                pointers.refs[idx] = value;
+                pointers.raw[idx] = value.GetPtr();
+            }
+
+            operator u8*() {
+                return pointers.raw[idx];
+            }
+
+        private:
+            Pointers& pointers;
+            VAddr idx;
+        };
+
+        Entry operator[](VAddr idx) {
+            return Entry(*this, idx);
+        }
+
+        u8* operator[](VAddr idx) const {
+            return raw[idx];
+        }
+
+        Entry operator[](std::size_t idx) {
+            return Entry(*this, static_cast<VAddr>(idx));
+        }
+
+    private:
+        std::array<u8*, PAGE_TABLE_NUM_ENTRIES> raw;
+
+        std::array<MemoryRef, PAGE_TABLE_NUM_ENTRIES> refs;
+
+        friend struct PageTable;
+    };
+    Pointers pointers;
 
     /**
      * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of
@@ -78,6 +131,24 @@ struct PageTable {
      * the corresponding entry in `pointers` MUST be set to null.
      */
     std::array<PageType, PAGE_TABLE_NUM_ENTRIES> attributes;
+
+    std::array<u8*, PAGE_TABLE_NUM_ENTRIES>& GetPointerArray() {
+        return pointers.raw;
+    }
+
+    void Clear();
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& pointers.refs;
+        ar& special_regions;
+        ar& attributes;
+        for (auto i = 0; i < PAGE_TABLE_NUM_ENTRIES; i++) {
+            pointers.raw[i] = pointers.refs[i].GetPtr();
+        }
+    }
+    friend class boost::serialization::access;
 };
 
 /// Physical memory regions as seen from the ARM11
@@ -121,6 +192,8 @@ enum : PAddr {
     FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE,
 };
 
+enum class Region { FCRAM, VRAM, DSP, N3DS };
+
 /// Virtual user-space memory regions
 enum : VAddr {
     /// Where the application text, data and bss reside.
@@ -209,6 +282,12 @@ enum class FlushMode {
     FlushAndInvalidate,
 };
 
+/**
+ * Flushes and invalidates all memory in the rasterizer cache and removes any leftover state
+ * If flush is true, the rasterizer should flush any cached resources to RAM before clearing
+ */
+void RasterizerClearAll(bool flush);
+
 /**
  * Flushes and invalidates any externally cached rasterizer resources touching the given virtual
  * address region.
@@ -228,7 +307,7 @@ public:
      * @param size The amount of bytes to map. Must be page-aligned.
      * @param target Buffer with the memory backing the mapping. Must be of length at least `size`.
      */
-    void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target);
+    void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, MemoryRef target);
 
     /**
      * Maps a region of the emulated process address space as a IO region.
@@ -242,8 +321,8 @@ public:
     void UnmapRegion(PageTable& page_table, VAddr base, u32 size);
 
     /// Currently active page table
-    void SetCurrentPageTable(PageTable* page_table);
-    PageTable* GetCurrentPageTable() const;
+    void SetCurrentPageTable(std::shared_ptr<PageTable> page_table);
+    std::shared_ptr<PageTable> GetCurrentPageTable() const;
 
     u8 Read8(VAddr addr);
     u16 Read16(VAddr addr);
@@ -272,26 +351,31 @@ public:
      */
     u8* GetPhysicalPointer(PAddr address);
 
+    MemoryRef GetPhysicalRef(PAddr address);
+
     u8* GetPointer(VAddr vaddr);
 
     bool IsValidPhysicalAddress(PAddr paddr);
 
     /// Gets offset in FCRAM from a pointer inside FCRAM range
-    u32 GetFCRAMOffset(u8* pointer);
+    u32 GetFCRAMOffset(const u8* pointer);
 
     /// Gets pointer in FCRAM with given offset
     u8* GetFCRAMPointer(u32 offset);
 
+    /// Gets a serializable ref to FCRAM with the given offset
+    MemoryRef GetFCRAMRef(u32 offset);
+
     /**
      * Mark each page touching the region as cached.
      */
     void RasterizerMarkRegionCached(PAddr start, u32 size, bool cached);
 
     /// Registers page table for rasterizer cache marking
-    void RegisterPageTable(PageTable* page_table);
+    void RegisterPageTable(std::shared_ptr<PageTable> page_table);
 
     /// Unregisters page table for rasterizer cache marking
-    void UnregisterPageTable(PageTable* page_table);
+    void UnregisterPageTable(std::shared_ptr<PageTable> page_table);
 
     void SetDSP(AudioCore::DspInterface& dsp);
 
@@ -308,16 +392,29 @@ private:
      * Since the cache only happens on linear heap or VRAM, we know the exact physical address and
      * pointer of such virtual address
      */
-    u8* GetPointerForRasterizerCache(VAddr addr);
+    MemoryRef GetPointerForRasterizerCache(VAddr addr);
 
-    void MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type);
+    void MapPages(PageTable& page_table, u32 base, u32 size, MemoryRef memory, PageType type);
 
     class Impl;
 
     std::unique_ptr<Impl> impl;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version);
+
+public:
+    template <Region R>
+    class BackingMemImpl;
 };
 
 /// Determines if the given VAddr is valid for the specified process.
 bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr);
 
 } // namespace Memory
+
+BOOST_CLASS_EXPORT_KEY(Memory::MemorySystem::BackingMemImpl<Memory::Region::FCRAM>)
+BOOST_CLASS_EXPORT_KEY(Memory::MemorySystem::BackingMemImpl<Memory::Region::VRAM>)
+BOOST_CLASS_EXPORT_KEY(Memory::MemorySystem::BackingMemImpl<Memory::Region::DSP>)
+BOOST_CLASS_EXPORT_KEY(Memory::MemorySystem::BackingMemImpl<Memory::Region::N3DS>)
diff --git a/src/core/mmio.h b/src/core/mmio.h
index 30bafaf5f..2e6323b49 100644
--- a/src/core/mmio.h
+++ b/src/core/mmio.h
@@ -32,6 +32,11 @@ public:
     virtual void Write64(VAddr addr, u64 data) = 0;
 
     virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, std::size_t size) = 0;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {}
 };
 
 using MMIORegionPointer = std::shared_ptr<MMIORegion>;
diff --git a/src/core/movie.h b/src/core/movie.h
index f1be86946..eff148090 100644
--- a/src/core/movie.h
+++ b/src/core/movie.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <functional>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
 
 namespace Service {
@@ -132,5 +133,16 @@ private:
     u64 init_time;
     std::function<void()> playback_completion_callback;
     std::size_t current_byte = 0;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        // Only serialize what's needed to make savestates useful for TAS:
+        u64 _current_byte = static_cast<u64>(current_byte);
+        ar& _current_byte;
+        current_byte = static_cast<std::size_t>(_current_byte);
+        ar& recorded_input;
+        ar& init_time;
+    }
+    friend class boost::serialization::access;
 };
 } // namespace Core
\ No newline at end of file
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 94d6c17ca..00ab24dbc 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -117,6 +117,14 @@ double PerfStats::GetLastFrameTimeScale() {
     return duration_cast<DoubleSecs>(previous_frame_length).count() / FRAME_LENGTH;
 }
 
+void FrameLimiter::WaitOnce() {
+    if (frame_advancing_enabled) {
+        // Frame advancing is enabled: wait on event instead of doing framelimiting
+        frame_advance_event.Wait();
+        frame_advance_event.Reset();
+    }
+}
+
 void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
     if (frame_advancing_enabled) {
         // Frame advancing is enabled: wait on event instead of doing framelimiting
@@ -164,10 +172,6 @@ void FrameLimiter::SetFrameAdvancing(bool value) {
 }
 
 void FrameLimiter::AdvanceFrame() {
-    if (!frame_advancing_enabled) {
-        // Start frame advancing
-        frame_advancing_enabled = true;
-    }
     frame_advance_event.Set();
 }
 
diff --git a/src/core/perf_stats.h b/src/core/perf_stats.h
index 0ef5168fa..c7d22ef8e 100644
--- a/src/core/perf_stats.h
+++ b/src/core/perf_stats.h
@@ -98,6 +98,7 @@ public:
      */
     void SetFrameAdvancing(bool value);
     void AdvanceFrame();
+    void WaitOnce();
 
 private:
     /// Emulated system time (in microseconds) at the last limiter invocation
diff --git a/src/core/savestate.cpp b/src/core/savestate.cpp
new file mode 100644
index 000000000..26fc0cbed
--- /dev/null
+++ b/src/core/savestate.cpp
@@ -0,0 +1,156 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+#include <boost/serialization/binary_object.hpp>
+#include <cryptopp/hex.h>
+#include "common/archives.h"
+#include "common/logging/log.h"
+#include "common/scm_rev.h"
+#include "common/zstd_compression.h"
+#include "core/cheats/cheats.h"
+#include "core/core.h"
+#include "core/savestate.h"
+#include "network/network.h"
+#include "video_core/video_core.h"
+
+namespace Core {
+
+#pragma pack(push, 1)
+struct CSTHeader {
+    std::array<u8, 4> filetype;  /// Unique Identifier to check the file type (always "CST"0x1B)
+    u64_le program_id;           /// ID of the ROM being executed. Also called title_id
+    std::array<u8, 20> revision; /// Git hash of the revision this savestate was created with
+    u64_le time;                 /// The time when this save state was created
+
+    std::array<u8, 216> reserved; /// Make heading 256 bytes so it has consistent size
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& boost::serialization::binary_object(this, sizeof(CSTHeader));
+    }
+};
+static_assert(sizeof(CSTHeader) == 256, "CSTHeader should be 256 bytes");
+#pragma pack(pop)
+
+constexpr std::array<u8, 4> header_magic_bytes{{'C', 'S', 'T', 0x1B}};
+
+std::string GetSaveStatePath(u64 program_id, u32 slot) {
+    return fmt::format("{}{:016X}.{:02d}.cst", FileUtil::GetUserPath(FileUtil::UserPath::StatesDir),
+                       program_id, slot);
+}
+
+std::vector<SaveStateInfo> ListSaveStates(u64 program_id) {
+    std::vector<SaveStateInfo> result;
+    for (u32 slot = 1; slot <= SaveStateSlotCount; ++slot) {
+        const auto path = GetSaveStatePath(program_id, slot);
+        if (!FileUtil::Exists(path)) {
+            continue;
+        }
+
+        SaveStateInfo info;
+        info.slot = slot;
+
+        FileUtil::IOFile file(path, "rb");
+        if (!file) {
+            LOG_ERROR(Core, "Could not open file {}", path);
+            continue;
+        }
+        CSTHeader header;
+        if (file.GetSize() < sizeof(header)) {
+            LOG_ERROR(Core, "File too small {}", path);
+            continue;
+        }
+        if (file.ReadBytes(&header, sizeof(header)) != sizeof(header)) {
+            LOG_ERROR(Core, "Could not read from file {}", path);
+            continue;
+        }
+        if (header.filetype != header_magic_bytes) {
+            LOG_WARNING(Core, "Invalid save state file {}", path);
+            continue;
+        }
+        info.time = header.time;
+
+        if (header.program_id != program_id) {
+            LOG_WARNING(Core, "Save state file isn't for the current game {}", path);
+            continue;
+        }
+        std::string revision = fmt::format("{:02x}", fmt::join(header.revision, ""));
+        if (revision == Common::g_scm_rev) {
+            info.status = SaveStateInfo::ValidationStatus::OK;
+        } else {
+            LOG_WARNING(Core, "Save state file created from a different revision {}", path);
+            info.status = SaveStateInfo::ValidationStatus::RevisionDismatch;
+        }
+        result.emplace_back(std::move(info));
+    }
+    return result;
+}
+
+void System::SaveState(u32 slot) const {
+    std::ostringstream sstream{std::ios_base::binary};
+    // Serialize
+    oarchive oa{sstream};
+    oa&* this;
+
+    const std::string& str{sstream.str()};
+    auto buffer = Common::Compression::CompressDataZSTDDefault(
+        reinterpret_cast<const u8*>(str.data()), str.size());
+
+    const auto path = GetSaveStatePath(title_id, slot);
+    if (!FileUtil::CreateFullPath(path)) {
+        throw std::runtime_error("Could not create path " + path);
+    }
+
+    FileUtil::IOFile file(path, "wb");
+    if (!file) {
+        throw std::runtime_error("Could not open file " + path);
+    }
+
+    CSTHeader header{};
+    header.filetype = header_magic_bytes;
+    header.program_id = title_id;
+    std::string rev_bytes;
+    CryptoPP::StringSource(Common::g_scm_rev, true,
+                           new CryptoPP::HexDecoder(new CryptoPP::StringSink(rev_bytes)));
+    std::memcpy(header.revision.data(), rev_bytes.data(), sizeof(header.revision));
+    header.time = std::chrono::duration_cast<std::chrono::seconds>(
+                      std::chrono::system_clock::now().time_since_epoch())
+                      .count();
+
+    if (file.WriteBytes(&header, sizeof(header)) != sizeof(header) ||
+        file.WriteBytes(buffer.data(), buffer.size()) != buffer.size()) {
+        throw std::runtime_error("Could not write to file " + path);
+    }
+}
+
+void System::LoadState(u32 slot) {
+    if (Network::GetRoomMember().lock()->IsConnected()) {
+        throw std::runtime_error("Unable to load while connected to multiplayer");
+    }
+
+    const auto path = GetSaveStatePath(title_id, slot);
+
+    std::vector<u8> decompressed;
+    {
+        std::vector<u8> buffer(FileUtil::GetSize(path) - sizeof(CSTHeader));
+
+        FileUtil::IOFile file(path, "rb");
+        file.Seek(sizeof(CSTHeader), SEEK_SET); // Skip header
+        if (file.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
+            throw std::runtime_error("Could not read from file at " + path);
+        }
+        decompressed = Common::Compression::DecompressDataZSTD(buffer);
+    }
+    std::istringstream sstream{
+        std::string{reinterpret_cast<char*>(decompressed.data()), decompressed.size()},
+        std::ios_base::binary};
+    decompressed.clear();
+
+    // Deserialize
+    iarchive ia{sstream};
+    ia&* this;
+}
+
+} // namespace Core
diff --git a/src/core/savestate.h b/src/core/savestate.h
new file mode 100644
index 000000000..f67bee22f
--- /dev/null
+++ b/src/core/savestate.h
@@ -0,0 +1,27 @@
+// Copyright 2020 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+
+namespace Core {
+
+struct CSTHeader;
+
+struct SaveStateInfo {
+    u32 slot;
+    u64 time;
+    enum class ValidationStatus {
+        OK,
+        RevisionDismatch,
+    } status;
+};
+
+constexpr u32 SaveStateSlotCount = 10; // Maximum count of savestate slots
+
+std::vector<SaveStateInfo> ListSaveStates(u64 program_id);
+
+} // namespace Core
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
index 24f782653..1e0eb4bc9 100644
--- a/src/network/CMakeLists.txt
+++ b/src/network/CMakeLists.txt
@@ -13,4 +13,4 @@ add_library(network STATIC
 
 create_target_directory_groups(network)
 
-target_link_libraries(network PRIVATE common enet)
+target_link_libraries(network PRIVATE common enet Boost::serialization)
diff --git a/src/network/room_member.h b/src/network/room_member.h
index ad5d14b44..ee1c921d4 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <string>
 #include <vector>
+#include <boost/serialization/vector.hpp>
 #include "common/common_types.h"
 #include "network/room.h"
 
@@ -30,6 +31,17 @@ struct WifiPacket {
     MacAddress transmitter_address; ///< Mac address of the transmitter.
     MacAddress destination_address; ///< Mac address of the receiver.
     u8 channel;                     ///< WiFi channel where this frame was transmitted.
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& type;
+        ar& data;
+        ar& transmitter_address;
+        ar& destination_address;
+        ar& channel;
+    }
+    friend class boost::serialization::access;
 };
 
 /// Represents a chat message.
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index a6fd0de76..1bba53c7e 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -10,7 +10,7 @@
 
 namespace ArmTests {
 
-static Memory::PageTable* page_table = nullptr;
+static std::shared_ptr<Memory::PageTable> page_table = nullptr;
 
 TestEnvironment::TestEnvironment(bool mutable_memory_)
     : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
@@ -20,10 +20,9 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
     kernel = std::make_unique<Kernel::KernelSystem>(*memory, *timing, [] {}, 0, 1, 0);
 
     kernel->SetCurrentProcess(kernel->CreateProcess(kernel->CreateCodeSet("", 0)));
-    page_table = &kernel->GetCurrentProcess()->vm_manager.page_table;
+    page_table = kernel->GetCurrentProcess()->vm_manager.page_table;
 
-    page_table->pointers.fill(nullptr);
-    page_table->attributes.fill(Memory::PageType::Unmapped);
+    page_table->Clear();
 
     memory->MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
     memory->MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp
index cb73d481f..f5623c8c1 100644
--- a/src/tests/core/hle/kernel/hle_ipc.cpp
+++ b/src/tests/core/hle/kernel/hle_ipc.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <catch2/catch.hpp>
+#include "common/archives.h"
 #include "core/core.h"
 #include "core/core_timing.h"
 #include "core/hle/ipc.h"
@@ -34,7 +35,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             IPC::MakeHeader(0x1234, 0, 0),
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
         REQUIRE(context.CommandBuffer()[0] == 0x12340000);
     }
@@ -47,7 +48,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             0xAABBCCDD,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
         auto* output = context.CommandBuffer();
         REQUIRE(output[1] == 0x12345678);
@@ -64,7 +65,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             a_handle,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
         auto* output = context.CommandBuffer();
         REQUIRE(context.GetIncomingHandle(output[2]) == a);
@@ -80,7 +81,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             a_handle,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
         auto* output = context.CommandBuffer();
         REQUIRE(context.GetIncomingHandle(output[2]) == a);
@@ -100,7 +101,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             process->handle_table.Create(c).Unwrap(),
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
         auto* output = context.CommandBuffer();
         REQUIRE(context.GetIncomingHandle(output[2]) == a);
@@ -115,7 +116,7 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             0,
         };
 
-        auto result = context.PopulateFromIncomingCommandBuffer(input, *process);
+        auto result = context.PopulateFromIncomingCommandBuffer(input, process);
 
         REQUIRE(result == RESULT_SUCCESS);
         auto* output = context.CommandBuffer();
@@ -129,73 +130,76 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             0x98989898,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
         REQUIRE(context.CommandBuffer()[2] == process->process_id);
     }
 
     SECTION("translates StaticBuffer descriptors") {
-        auto buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE);
-        std::fill(buffer->begin(), buffer->end(), 0xAB);
+        auto mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE);
+        MemoryRef buffer{mem};
+        std::fill(buffer.GetPtr(), buffer.GetPtr() + buffer.GetSize(), 0xAB);
 
         VAddr target_address = 0x10000000;
-        auto result = process->vm_manager.MapBackingMemory(target_address, buffer->data(),
-                                                           buffer->size(), MemoryState::Private);
+        auto result = process->vm_manager.MapBackingMemory(target_address, buffer, buffer.GetSize(),
+                                                           MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
         const u32_le input[]{
             IPC::MakeHeader(0, 0, 2),
-            IPC::StaticBufferDesc(buffer->size(), 0),
+            IPC::StaticBufferDesc(buffer.GetSize(), 0),
             target_address,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
-        CHECK(context.GetStaticBuffer(0) == *buffer);
+        CHECK(context.GetStaticBuffer(0) == mem->Vector());
 
-        REQUIRE(process->vm_manager.UnmapRange(target_address, buffer->size()) == RESULT_SUCCESS);
+        REQUIRE(process->vm_manager.UnmapRange(target_address, buffer.GetSize()) == RESULT_SUCCESS);
     }
 
     SECTION("translates MappedBuffer descriptors") {
-        auto buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE);
-        std::fill(buffer->begin(), buffer->end(), 0xCD);
+        auto mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE);
+        MemoryRef buffer{mem};
+        std::fill(buffer.GetPtr(), buffer.GetPtr() + buffer.GetSize(), 0xCD);
 
         VAddr target_address = 0x10000000;
-        auto result = process->vm_manager.MapBackingMemory(target_address, buffer->data(),
-                                                           buffer->size(), MemoryState::Private);
+        auto result = process->vm_manager.MapBackingMemory(target_address, buffer, buffer.GetSize(),
+                                                           MemoryState::Private);
 
         const u32_le input[]{
             IPC::MakeHeader(0, 0, 2),
-            IPC::MappedBufferDesc(buffer->size(), IPC::R),
+            IPC::MappedBufferDesc(buffer.GetSize(), IPC::R),
             target_address,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
-        std::vector<u8> other_buffer(buffer->size());
-        context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer->size());
+        std::vector<u8> other_buffer(buffer.GetSize());
+        context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer.GetSize());
 
-        CHECK(other_buffer == *buffer);
+        CHECK(other_buffer == mem->Vector());
 
-        REQUIRE(process->vm_manager.UnmapRange(target_address, buffer->size()) == RESULT_SUCCESS);
+        REQUIRE(process->vm_manager.UnmapRange(target_address, buffer.GetSize()) == RESULT_SUCCESS);
     }
 
     SECTION("translates mixed params") {
-        auto buffer_static = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE);
-        std::fill(buffer_static->begin(), buffer_static->end(), 0xCE);
+        auto mem_static = std::make_shared<BufferMem>(Memory::PAGE_SIZE);
+        MemoryRef buffer_static{mem_static};
+        std::fill(buffer_static.GetPtr(), buffer_static.GetPtr() + buffer_static.GetSize(), 0xCE);
 
-        auto buffer_mapped = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE);
-        std::fill(buffer_mapped->begin(), buffer_mapped->end(), 0xDF);
+        auto mem_mapped = std::make_shared<BufferMem>(Memory::PAGE_SIZE);
+        MemoryRef buffer_mapped{mem_mapped};
+        std::fill(buffer_mapped.GetPtr(), buffer_mapped.GetPtr() + buffer_mapped.GetSize(), 0xDF);
 
         VAddr target_address_static = 0x10000000;
-        auto result =
-            process->vm_manager.MapBackingMemory(target_address_static, buffer_static->data(),
-                                                 buffer_static->size(), MemoryState::Private);
+        auto result = process->vm_manager.MapBackingMemory(
+            target_address_static, buffer_static, buffer_static.GetSize(), MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
         VAddr target_address_mapped = 0x20000000;
-        result = process->vm_manager.MapBackingMemory(target_address_mapped, buffer_mapped->data(),
-                                                      buffer_mapped->size(), MemoryState::Private);
+        result = process->vm_manager.MapBackingMemory(
+            target_address_mapped, buffer_mapped, buffer_mapped.GetSize(), MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
         auto a = MakeObject(kernel);
@@ -207,27 +211,27 @@ TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel
             process->handle_table.Create(a).Unwrap(),
             IPC::CallingPidDesc(),
             0,
-            IPC::StaticBufferDesc(buffer_static->size(), 0),
+            IPC::StaticBufferDesc(buffer_static.GetSize(), 0),
             target_address_static,
-            IPC::MappedBufferDesc(buffer_mapped->size(), IPC::R),
+            IPC::MappedBufferDesc(buffer_mapped.GetSize(), IPC::R),
             target_address_mapped,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input, *process);
+        context.PopulateFromIncomingCommandBuffer(input, process);
 
         auto* output = context.CommandBuffer();
         CHECK(output[1] == 0x12345678);
         CHECK(output[2] == 0xABCDEF00);
         CHECK(context.GetIncomingHandle(output[4]) == a);
         CHECK(output[6] == process->process_id);
-        CHECK(context.GetStaticBuffer(0) == *buffer_static);
-        std::vector<u8> other_buffer(buffer_mapped->size());
-        context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer_mapped->size());
-        CHECK(other_buffer == *buffer_mapped);
+        CHECK(context.GetStaticBuffer(0) == mem_static->Vector());
+        std::vector<u8> other_buffer(buffer_mapped.GetSize());
+        context.GetMappedBuffer(0).Read(other_buffer.data(), 0, buffer_mapped.GetSize());
+        CHECK(other_buffer == mem_mapped->Vector());
 
-        REQUIRE(process->vm_manager.UnmapRange(target_address_static, buffer_static->size()) ==
+        REQUIRE(process->vm_manager.UnmapRange(target_address_static, buffer_static.GetSize()) ==
                 RESULT_SUCCESS);
-        REQUIRE(process->vm_manager.UnmapRange(target_address_mapped, buffer_mapped->size()) ==
+        REQUIRE(process->vm_manager.UnmapRange(target_address_mapped, buffer_mapped.GetSize()) ==
                 RESULT_SUCCESS);
     }
 }
@@ -314,10 +318,12 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
 
         context.AddStaticBuffer(0, input_buffer);
 
-        auto output_buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE);
+        auto output_mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE);
+        MemoryRef output_buffer{output_mem};
+
         VAddr target_address = 0x10000000;
         auto result = process->vm_manager.MapBackingMemory(
-            target_address, output_buffer->data(), output_buffer->size(), MemoryState::Private);
+            target_address, output_buffer, output_buffer.GetSize(), MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
         input[0] = IPC::MakeHeader(0, 0, 2);
@@ -329,13 +335,13 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
         std::array<u32_le, IPC::COMMAND_BUFFER_LENGTH + 2> output_cmdbuff;
         // Set up the output StaticBuffer
         output_cmdbuff[IPC::COMMAND_BUFFER_LENGTH] =
-            IPC::StaticBufferDesc(output_buffer->size(), 0);
+            IPC::StaticBufferDesc(output_buffer.GetSize(), 0);
         output_cmdbuff[IPC::COMMAND_BUFFER_LENGTH + 1] = target_address;
 
         context.WriteToOutgoingCommandBuffer(output_cmdbuff.data(), *process);
 
-        CHECK(*output_buffer == input_buffer);
-        REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer->size()) ==
+        CHECK(output_mem->Vector() == input_buffer);
+        REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer.GetSize()) ==
                 RESULT_SUCCESS);
     }
 
@@ -343,32 +349,34 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
         std::vector<u8> input_buffer(Memory::PAGE_SIZE);
         std::fill(input_buffer.begin(), input_buffer.end(), 0xAB);
 
-        auto output_buffer = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE);
+        auto output_mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE);
+        MemoryRef output_buffer{output_mem};
+
         VAddr target_address = 0x10000000;
         auto result = process->vm_manager.MapBackingMemory(
-            target_address, output_buffer->data(), output_buffer->size(), MemoryState::Private);
+            target_address, output_buffer, output_buffer.GetSize(), MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
         const u32_le input_cmdbuff[]{
             IPC::MakeHeader(0, 0, 2),
-            IPC::MappedBufferDesc(output_buffer->size(), IPC::W),
+            IPC::MappedBufferDesc(output_buffer.GetSize(), IPC::W),
             target_address,
         };
 
-        context.PopulateFromIncomingCommandBuffer(input_cmdbuff, *process);
+        context.PopulateFromIncomingCommandBuffer(input_cmdbuff, process);
 
         context.GetMappedBuffer(0).Write(input_buffer.data(), 0, input_buffer.size());
 
         input[0] = IPC::MakeHeader(0, 0, 2);
-        input[1] = IPC::MappedBufferDesc(output_buffer->size(), IPC::W);
+        input[1] = IPC::MappedBufferDesc(output_buffer.GetSize(), IPC::W);
         input[2] = 0;
 
         context.WriteToOutgoingCommandBuffer(output, *process);
 
-        CHECK(output[1] == IPC::MappedBufferDesc(output_buffer->size(), IPC::W));
+        CHECK(output[1] == IPC::MappedBufferDesc(output_buffer.GetSize(), IPC::W));
         CHECK(output[2] == target_address);
-        CHECK(*output_buffer == input_buffer);
-        REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer->size()) ==
+        CHECK(output_mem->Vector() == input_buffer);
+        REQUIRE(process->vm_manager.UnmapRange(target_address, output_buffer.GetSize()) ==
                 RESULT_SUCCESS);
     }
 }
diff --git a/src/tests/core/memory/vm_manager.cpp b/src/tests/core/memory/vm_manager.cpp
index bd510864a..5a8e8b788 100644
--- a/src/tests/core/memory/vm_manager.cpp
+++ b/src/tests/core/memory/vm_manager.cpp
@@ -10,47 +10,48 @@
 #include "core/memory.h"
 
 TEST_CASE("Memory Basics", "[kernel][memory]") {
-    auto block = std::make_shared<std::vector<u8>>(Memory::PAGE_SIZE);
+    auto mem = std::make_shared<BufferMem>(Memory::PAGE_SIZE);
+    MemoryRef block{mem};
     Memory::MemorySystem memory;
     SECTION("mapping memory") {
         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
         auto manager = std::make_unique<Kernel::VMManager>(memory);
-        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(),
+        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(),
                                                 Kernel::MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
         auto vma = manager->FindVMA(Memory::HEAP_VADDR);
         CHECK(vma != manager->vma_map.end());
-        CHECK(vma->second.size == block->size());
+        CHECK(vma->second.size == block.GetSize());
         CHECK(vma->second.type == Kernel::VMAType::BackingMemory);
-        CHECK(vma->second.backing_memory == block->data());
+        CHECK(vma->second.backing_memory.GetPtr() == block.GetPtr());
         CHECK(vma->second.meminfo_state == Kernel::MemoryState::Private);
     }
 
     SECTION("unmapping memory") {
         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
         auto manager = std::make_unique<Kernel::VMManager>(memory);
-        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(),
+        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(),
                                                 Kernel::MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
-        ResultCode code = manager->UnmapRange(Memory::HEAP_VADDR, block->size());
+        ResultCode code = manager->UnmapRange(Memory::HEAP_VADDR, block.GetSize());
         REQUIRE(code == RESULT_SUCCESS);
 
         auto vma = manager->FindVMA(Memory::HEAP_VADDR);
         CHECK(vma != manager->vma_map.end());
         CHECK(vma->second.type == Kernel::VMAType::Free);
-        CHECK(vma->second.backing_memory == nullptr);
+        CHECK(vma->second.backing_memory.GetPtr() == nullptr);
     }
 
     SECTION("changing memory permissions") {
         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
         auto manager = std::make_unique<Kernel::VMManager>(memory);
-        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(),
+        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(),
                                                 Kernel::MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
-        ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block->size(),
+        ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block.GetSize(),
                                                   Kernel::VMAPermission::Execute);
         CHECK(code == RESULT_SUCCESS);
 
@@ -58,24 +59,24 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
         CHECK(vma != manager->vma_map.end());
         CHECK(vma->second.permissions == Kernel::VMAPermission::Execute);
 
-        code = manager->UnmapRange(Memory::HEAP_VADDR, block->size());
+        code = manager->UnmapRange(Memory::HEAP_VADDR, block.GetSize());
         REQUIRE(code == RESULT_SUCCESS);
     }
 
     SECTION("changing memory state") {
         // Because of the PageTable, Kernel::VMManager is too big to be created on the stack.
         auto manager = std::make_unique<Kernel::VMManager>(memory);
-        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block->data(), block->size(),
+        auto result = manager->MapBackingMemory(Memory::HEAP_VADDR, block, block.GetSize(),
                                                 Kernel::MemoryState::Private);
         REQUIRE(result.Code() == RESULT_SUCCESS);
 
-        ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block->size(),
+        ResultCode code = manager->ReprotectRange(Memory::HEAP_VADDR, block.GetSize(),
                                                   Kernel::VMAPermission::ReadWrite);
         REQUIRE(code == RESULT_SUCCESS);
 
         SECTION("with invalid address") {
             ResultCode code = manager->ChangeMemoryState(
-                0xFFFFFFFF, block->size(), Kernel::MemoryState::Locked,
+                0xFFFFFFFF, block.GetSize(), Kernel::MemoryState::Locked,
                 Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased,
                 Kernel::VMAPermission::Execute);
             CHECK(code == Kernel::ERR_INVALID_ADDRESS);
@@ -83,7 +84,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
 
         SECTION("ignoring the original permissions") {
             ResultCode code = manager->ChangeMemoryState(
-                Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private,
+                Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Private,
                 Kernel::VMAPermission::None, Kernel::MemoryState::Locked,
                 Kernel::VMAPermission::Write);
             CHECK(code == RESULT_SUCCESS);
@@ -96,7 +97,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
 
         SECTION("enforcing the original permissions with correct expectations") {
             ResultCode code = manager->ChangeMemoryState(
-                Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private,
+                Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Private,
                 Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased,
                 Kernel::VMAPermission::Execute);
             CHECK(code == RESULT_SUCCESS);
@@ -109,7 +110,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
 
         SECTION("with incorrect permission expectations") {
             ResultCode code = manager->ChangeMemoryState(
-                Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Private,
+                Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Private,
                 Kernel::VMAPermission::Execute, Kernel::MemoryState::Aliased,
                 Kernel::VMAPermission::Execute);
             CHECK(code == Kernel::ERR_INVALID_ADDRESS_STATE);
@@ -122,7 +123,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
 
         SECTION("with incorrect state expectations") {
             ResultCode code = manager->ChangeMemoryState(
-                Memory::HEAP_VADDR, block->size(), Kernel::MemoryState::Locked,
+                Memory::HEAP_VADDR, block.GetSize(), Kernel::MemoryState::Locked,
                 Kernel::VMAPermission::ReadWrite, Kernel::MemoryState::Aliased,
                 Kernel::VMAPermission::Execute);
             CHECK(code == Kernel::ERR_INVALID_ADDRESS_STATE);
@@ -133,7 +134,7 @@ TEST_CASE("Memory Basics", "[kernel][memory]") {
             CHECK(vma->second.meminfo_state == Kernel::MemoryState::Private);
         }
 
-        code = manager->UnmapRange(Memory::HEAP_VADDR, block->size());
+        code = manager->UnmapRange(Memory::HEAP_VADDR, block.GetSize());
         REQUIRE(code == RESULT_SUCCESS);
     }
 }
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 6c827c5ce..a6f9860eb 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -142,7 +142,7 @@ endif()
 create_target_directory_groups(video_core)
 
 target_link_libraries(video_core PUBLIC common core)
-target_link_libraries(video_core PRIVATE glad nihstro-headers)
+target_link_libraries(video_core PRIVATE glad nihstro-headers Boost::serialization)
 
 if (ARCHITECTURE_x86_64)
     target_link_libraries(video_core PUBLIC xbyak)
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index 72f6f2161..1c633cf5c 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -640,8 +640,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
                                  reinterpret_cast<void*>(&id));
 }
 
-void ProcessCommandList(const u32* list, u32 size) {
-    g_state.cmd_list.head_ptr = g_state.cmd_list.current_ptr = list;
+void ProcessCommandList(PAddr list, u32 size) {
+
+    u32* buffer = (u32*)VideoCore::g_memory->GetPhysicalPointer(list);
+
+    if (Pica::g_debug_context && Pica::g_debug_context->recorder) {
+        Pica::g_debug_context->recorder->MemoryAccessed((u8*)buffer, size, list);
+    }
+
+    g_state.cmd_list.addr = list;
+    g_state.cmd_list.head_ptr = g_state.cmd_list.current_ptr = buffer;
     g_state.cmd_list.length = size / sizeof(u32);
 
     while (g_state.cmd_list.current_ptr < g_state.cmd_list.head_ptr + g_state.cmd_list.length) {
diff --git a/src/video_core/command_processor.h b/src/video_core/command_processor.h
index 82b154327..3b4e05519 100644
--- a/src/video_core/command_processor.h
+++ b/src/video_core/command_processor.h
@@ -32,6 +32,6 @@ static_assert(std::is_standard_layout<CommandHeader>::value == true,
               "CommandHeader does not use standard layout");
 static_assert(sizeof(CommandHeader) == sizeof(u32), "CommandHeader has incorrect size!");
 
-void ProcessCommandList(const u32* list, u32 size);
+void ProcessCommandList(PAddr list, u32 size);
 
 } // namespace Pica::CommandProcessor
diff --git a/src/video_core/geometry_pipeline.cpp b/src/video_core/geometry_pipeline.cpp
index 3a24b71c9..0ddbda943 100644
--- a/src/video_core/geometry_pipeline.cpp
+++ b/src/video_core/geometry_pipeline.cpp
@@ -2,6 +2,10 @@
 // Licensed under GPLv2 or any later version
 // Refer to the license.txt file included.
 
+#include <boost/serialization/base_object.hpp>
+#include <boost/serialization/export.hpp>
+#include <boost/serialization/unique_ptr.hpp>
+#include "common/archives.h"
 #include "video_core/geometry_pipeline.h"
 #include "video_core/pica_state.h"
 #include "video_core/regs.h"
@@ -30,6 +34,11 @@ public:
      * @return if the buffer is full and the geometry shader should be invoked
      */
     virtual bool SubmitVertex(const Shader::AttributeBuffer& input) = 0;
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {}
+    friend class boost::serialization::access;
 };
 
 // In the Point mode, vertex attributes are sent to the input registers in the geometry shader unit.
@@ -79,6 +88,38 @@ private:
     Common::Vec4<float24>* buffer_cur;
     Common::Vec4<float24>* buffer_end;
     unsigned int vs_output_num;
+
+    GeometryPipeline_Point() : regs(g_state.regs), unit(g_state.gs_unit) {}
+
+    template <typename Class, class Archive>
+    static void serialize_common(Class* self, Archive& ar, const unsigned int version) {
+        ar& boost::serialization::base_object<GeometryPipelineBackend>(*self);
+        ar & self->attribute_buffer;
+        ar & self->vs_output_num;
+    }
+
+    template <class Archive>
+    void save(Archive& ar, const unsigned int version) const {
+        serialize_common(this, ar, version);
+        auto buffer_idx = static_cast<u32>(buffer_cur - attribute_buffer.attr);
+        auto buffer_size = static_cast<u32>(buffer_end - attribute_buffer.attr);
+        ar << buffer_idx;
+        ar << buffer_size;
+    }
+
+    template <class Archive>
+    void load(Archive& ar, const unsigned int version) {
+        serialize_common(this, ar, version);
+        u32 buffer_idx, buffer_size;
+        ar >> buffer_idx;
+        ar >> buffer_size;
+        buffer_cur = attribute_buffer.attr + buffer_idx;
+        buffer_end = attribute_buffer.attr + buffer_size;
+    }
+
+    BOOST_SERIALIZATION_SPLIT_MEMBER()
+
+    friend class boost::serialization::access;
 };
 
 // In VariablePrimitive mode, vertex attributes are buffered into the uniform registers in the
@@ -144,6 +185,36 @@ private:
     unsigned int total_vertex_num;
     Common::Vec4<float24>* buffer_cur;
     unsigned int vs_output_num;
+
+    GeometryPipeline_VariablePrimitive() : regs(g_state.regs), setup(g_state.gs) {}
+
+    template <typename Class, class Archive>
+    static void serialize_common(Class* self, Archive& ar, const unsigned int version) {
+        ar& boost::serialization::base_object<GeometryPipelineBackend>(*self);
+        ar & self->need_index;
+        ar & self->main_vertex_num;
+        ar & self->total_vertex_num;
+        ar & self->vs_output_num;
+    }
+
+    template <class Archive>
+    void save(Archive& ar, const unsigned int version) const {
+        serialize_common(this, ar, version);
+        auto buffer_idx = static_cast<u32>(buffer_cur - setup.uniforms.f);
+        ar << buffer_idx;
+    }
+
+    template <class Archive>
+    void load(Archive& ar, const unsigned int version) {
+        serialize_common(this, ar, version);
+        u32 buffer_idx;
+        ar >> buffer_idx;
+        buffer_cur = setup.uniforms.f + buffer_idx;
+    }
+
+    BOOST_SERIALIZATION_SPLIT_MEMBER()
+
+    friend class boost::serialization::access;
 };
 
 // In FixedPrimitive mode, vertex attributes are buffered into the uniform registers in the geometry
@@ -190,6 +261,41 @@ private:
     Common::Vec4<float24>* buffer_cur;
     Common::Vec4<float24>* buffer_end;
     unsigned int vs_output_num;
+
+    GeometryPipeline_FixedPrimitive() : regs(g_state.regs), setup(g_state.gs) {}
+
+    template <typename Class, class Archive>
+    static void serialize_common(Class* self, Archive& ar, const unsigned int version) {
+        ar& boost::serialization::base_object<GeometryPipelineBackend>(*self);
+        ar & self->vs_output_num;
+    }
+
+    template <class Archive>
+    void save(Archive& ar, const unsigned int version) const {
+        serialize_common(this, ar, version);
+        auto buffer_offset = static_cast<u32>(buffer_begin - setup.uniforms.f);
+        auto buffer_idx = static_cast<u32>(buffer_cur - setup.uniforms.f);
+        auto buffer_size = static_cast<u32>(buffer_end - setup.uniforms.f);
+        ar << buffer_offset;
+        ar << buffer_idx;
+        ar << buffer_size;
+    }
+
+    template <class Archive>
+    void load(Archive& ar, const unsigned int version) {
+        serialize_common(this, ar, version);
+        u32 buffer_offset, buffer_idx, buffer_size;
+        ar >> buffer_offset;
+        ar >> buffer_idx;
+        ar >> buffer_size;
+        buffer_begin = setup.uniforms.f + buffer_offset;
+        buffer_cur = setup.uniforms.f + buffer_idx;
+        buffer_end = setup.uniforms.f + buffer_size;
+    }
+
+    BOOST_SERIALIZATION_SPLIT_MEMBER()
+
+    friend class boost::serialization::access;
 };
 
 GeometryPipeline::GeometryPipeline(State& state) : state(state) {}
@@ -271,4 +377,15 @@ void GeometryPipeline::SubmitVertex(const Shader::AttributeBuffer& input) {
     }
 }
 
+template <class Archive>
+void GeometryPipeline::serialize(Archive& ar, const unsigned int version) {
+    // vertex_handler and shader_engine are always set to the same value
+    ar& backend;
+}
+
 } // namespace Pica
+
+SERIALIZE_EXPORT_IMPL(Pica::GeometryPipeline_Point)
+SERIALIZE_EXPORT_IMPL(Pica::GeometryPipeline_VariablePrimitive)
+SERIALIZE_EXPORT_IMPL(Pica::GeometryPipeline_FixedPrimitive)
+SERIALIZE_IMPL(Pica::GeometryPipeline)
diff --git a/src/video_core/geometry_pipeline.h b/src/video_core/geometry_pipeline.h
index 91fdd3192..1a903b1e0 100644
--- a/src/video_core/geometry_pipeline.h
+++ b/src/video_core/geometry_pipeline.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <memory>
+#include <boost/serialization/export.hpp>
 #include "video_core/shader/shader.h"
 
 namespace Pica {
@@ -12,6 +13,9 @@ namespace Pica {
 struct State;
 
 class GeometryPipelineBackend;
+class GeometryPipeline_Point;
+class GeometryPipeline_VariablePrimitive;
+class GeometryPipeline_FixedPrimitive;
 
 /// A pipeline receiving from vertex shader and sending to geometry shader and primitive assembler
 class GeometryPipeline {
@@ -45,5 +49,14 @@ private:
     Shader::ShaderEngine* shader_engine;
     std::unique_ptr<GeometryPipelineBackend> backend;
     State& state;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int version);
+
+    friend class boost::serialization::access;
 };
 } // namespace Pica
+
+BOOST_CLASS_EXPORT_KEY(Pica::GeometryPipeline_Point)
+BOOST_CLASS_EXPORT_KEY(Pica::GeometryPipeline_VariablePrimitive)
+BOOST_CLASS_EXPORT_KEY(Pica::GeometryPipeline_FixedPrimitive)
diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp
index 1475e3a92..5e0c2d480 100644
--- a/src/video_core/pica.cpp
+++ b/src/video_core/pica.cpp
@@ -3,12 +3,20 @@
 // Refer to the license.txt file included.
 
 #include <cstring>
+#include "core/global.h"
 #include "video_core/geometry_pipeline.h"
 #include "video_core/pica.h"
 #include "video_core/pica_state.h"
 #include "video_core/renderer_base.h"
 #include "video_core/video_core.h"
 
+namespace Core {
+template <>
+Pica::State& Global() {
+    return Pica::g_state;
+}
+} // namespace Core
+
 namespace Pica {
 
 State g_state;
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index 5a97ae952..3c29ff84e 100644
--- a/src/video_core/pica_state.h
+++ b/src/video_core/pica_state.h
@@ -5,6 +5,8 @@
 #pragma once
 
 #include <array>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/split_member.hpp>
 #include "common/bit_field.h"
 #include "common/common_types.h"
 #include "common/vector_math.h"
@@ -12,6 +14,20 @@
 #include "video_core/primitive_assembly.h"
 #include "video_core/regs.h"
 #include "video_core/shader/shader.h"
+#include "video_core/video_core.h"
+
+// Boost::serialization doesn't like union types for some reason,
+// so we need to mark arrays of union values with a special serialization method
+template <typename Value, size_t Size>
+struct UnionArray : public std::array<Value, Size> {
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        static_assert(sizeof(Value) == sizeof(u32));
+        ar&* static_cast<u32(*)[Size]>(static_cast<void*>(this->data()));
+    }
+    friend class boost::serialization::access;
+};
 
 namespace Pica {
 
@@ -74,11 +90,22 @@ struct State {
             }
         };
 
-        std::array<ValueEntry, 128> noise_table;
-        std::array<ValueEntry, 128> color_map_table;
-        std::array<ValueEntry, 128> alpha_map_table;
-        std::array<ColorEntry, 256> color_table;
-        std::array<ColorDifferenceEntry, 256> color_diff_table;
+        UnionArray<ValueEntry, 128> noise_table;
+        UnionArray<ValueEntry, 128> color_map_table;
+        UnionArray<ValueEntry, 128> alpha_map_table;
+        UnionArray<ColorEntry, 256> color_table;
+        UnionArray<ColorDifferenceEntry, 256> color_diff_table;
+
+    private:
+        friend class boost::serialization::access;
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int file_version) {
+            ar& noise_table;
+            ar& color_map_table;
+            ar& alpha_map_table;
+            ar& color_table;
+            ar& color_diff_table;
+        }
     } proctex;
 
     struct Lighting {
@@ -101,9 +128,14 @@ struct State {
                 float diff = static_cast<float>(difference) / 2047.f;
                 return neg_difference ? -diff : diff;
             }
+
+            template <class Archive>
+            void serialize(Archive& ar, const unsigned int file_version) {
+                ar& raw;
+            }
         };
 
-        std::array<std::array<LutEntry, 256>, 24> luts;
+        std::array<UnionArray<LutEntry, 256>, 24> luts;
     } lighting;
 
     struct {
@@ -123,11 +155,12 @@ struct State {
             }
         };
 
-        std::array<LutEntry, 128> lut;
+        UnionArray<LutEntry, 128> lut;
     } fog;
 
     /// Current Pica command list
     struct {
+        PAddr addr; // This exists only for serialization
         const u32* head_ptr;
         const u32* current_ptr;
         u32 length;
@@ -141,6 +174,16 @@ struct State {
         u32 current_attribute = 0;
         // Indicates the immediate mode just started and the geometry pipeline needs to reconfigure
         bool reset_geometry_pipeline = true;
+
+    private:
+        friend class boost::serialization::access;
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int file_version) {
+            ar& input_vertex;
+            ar& current_attribute;
+            ar& reset_geometry_pipeline;
+        }
+
     } immediate;
 
     // the geometry shader needs to be kept in the global state because some shaders relie on
@@ -161,6 +204,46 @@ struct State {
 
     int default_attr_counter = 0;
     u32 default_attr_write_buffer[3]{};
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& regs.reg_array;
+        ar& vs;
+        ar& gs;
+        ar& input_default_attributes;
+        ar& proctex;
+        ar& lighting.luts;
+        ar& fog.lut;
+        ar& cmd_list.addr;
+        ar& cmd_list.length;
+        ar& immediate;
+        ar& gs_unit;
+        ar& geometry_pipeline;
+        ar& primitive_assembler;
+        ar& vs_float_regs_counter;
+        ar& vs_uniform_write_buffer;
+        ar& gs_float_regs_counter;
+        ar& gs_uniform_write_buffer;
+        ar& default_attr_counter;
+        ar& default_attr_write_buffer;
+        boost::serialization::split_member(ar, *this, file_version);
+    }
+
+    template <class Archive>
+    void save(Archive& ar, const unsigned int file_version) const {
+        ar << static_cast<u32>(cmd_list.current_ptr - cmd_list.head_ptr);
+    }
+
+    template <class Archive>
+    void load(Archive& ar, const unsigned int file_version) {
+        u32 offset{};
+        ar >> offset;
+        cmd_list.head_ptr =
+            reinterpret_cast<u32*>(VideoCore::g_memory->GetPhysicalPointer(cmd_list.addr));
+        cmd_list.current_ptr = cmd_list.head_ptr + offset;
+    }
 };
 
 extern State g_state; ///< Current Pica state
diff --git a/src/video_core/pica_types.h b/src/video_core/pica_types.h
index 5aca37b69..33012c259 100644
--- a/src/video_core/pica_types.h
+++ b/src/video_core/pica_types.h
@@ -6,6 +6,7 @@
 
 #include <cmath>
 #include <cstring>
+#include <boost/serialization/access.hpp>
 #include "common/common_types.h"
 
 namespace Pica {
@@ -140,6 +141,12 @@ private:
     // Stored as a regular float, merely for convenience
     // TODO: Perform proper arithmetic on this!
     float value;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& value;
+    }
 };
 
 using float24 = Float<16, 7>;
diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h
index fd5445aa8..404bc5316 100644
--- a/src/video_core/primitive_assembly.h
+++ b/src/video_core/primitive_assembly.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <functional>
+#include <boost/serialization/access.hpp>
 #include "video_core/regs_pipeline.h"
 
 namespace Pica {
@@ -62,6 +63,16 @@ private:
     VertexType buffer[2];
     bool strip_ready = false;
     bool winding = false;
+
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int version) {
+        ar& topology;
+        ar& buffer_index;
+        ar& buffer;
+        ar& strip_ready;
+        ar& winding;
+    }
+    friend class boost::serialization::access;
 };
 
 } // namespace Pica
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 468d84084..873e4273e 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -55,6 +55,9 @@ public:
     /// and invalidated
     virtual void FlushAndInvalidateRegion(PAddr addr, u32 size) = 0;
 
+    /// Removes as much state as possible from the rasterizer in preparation for a save/load state
+    virtual void ClearAll(bool flush) = 0;
+
     /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
     virtual bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
         return false;
@@ -84,5 +87,7 @@ public:
 
     virtual void LoadDiskResources(const std::atomic_bool& stop_loading,
                                    const DiskResourceLoadCallback& callback) {}
+
+    virtual void SyncEntireState() {}
 };
 } // namespace VideoCore
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 4065d1a7d..8d18b6800 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -28,3 +28,7 @@ void RendererBase::RefreshRasterizerSetting() {
         }
     }
 }
+
+void RendererBase::Sync() {
+    rasterizer->SyncEntireState();
+}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 7fcaf5370..578939a6e 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -68,6 +68,7 @@ public:
     }
 
     void RefreshRasterizerSetting();
+    void Sync();
 
 protected:
     Frontend::EmuWindow& render_window; ///< Reference to the render window handle.
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 4ac9e36c2..b2fd23ff7 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1367,6 +1367,10 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) {
     res_cache.InvalidateRegion(addr, size, nullptr);
 }
 
+void RasterizerOpenGL::ClearAll(bool flush) {
+    res_cache.ClearAll(flush);
+}
+
 bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
     MICROPROFILE_SCOPE(OpenGL_Blits);
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index b3356a69b..fee8363b6 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -53,6 +53,7 @@ public:
     void FlushRegion(PAddr addr, u32 size) override;
     void InvalidateRegion(PAddr addr, u32 size) override;
     void FlushAndInvalidateRegion(PAddr addr, u32 size) override;
+    void ClearAll(bool flush) override;
     bool AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) override;
     bool AccelerateTextureCopy(const GPU::Regs::DisplayTransferConfig& config) override;
     bool AccelerateFill(const GPU::Regs::MemoryFillConfig& config) override;
@@ -60,6 +61,9 @@ public:
                            u32 pixel_stride, ScreenInfo& screen_info) override;
     bool AccelerateDrawBatch(bool is_indexed) override;
 
+    /// Syncs entire status to match PICA registers
+    void SyncEntireState() override;
+
 private:
     struct SamplerInfo {
         using TextureConfig = Pica::TexturingRegs::TextureConfig;
@@ -131,9 +135,6 @@ private:
         GLvec3 view;
     };
 
-    /// Syncs entire status to match PICA registers
-    void SyncEntireState();
-
     /// Syncs the clip enabled status to match the PICA register
     void SyncClipEnabled();
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index b8aaabf96..040e6e477 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -1071,9 +1071,7 @@ RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
 }
 
 RasterizerCacheOpenGL::~RasterizerCacheOpenGL() {
-    FlushAll();
-    while (!surface_cache.empty())
-        UnregisterSurface(*surface_cache.begin()->second.begin());
+    ClearAll(false);
 }
 
 MICROPROFILE_DEFINE(OpenGL_BlitSurface, "OpenGL", "BlitSurface", MP_RGB(128, 192, 64));
@@ -1767,6 +1765,31 @@ bool RasterizerCacheOpenGL::ValidateByReinterpretation(const Surface& surface,
     return false;
 }
 
+void RasterizerCacheOpenGL::ClearAll(bool flush) {
+    const auto flush_interval = PageMap::interval_type::right_open(0x0, 0xFFFFFFFF);
+    // Force flush all surfaces from the cache
+    if (flush) {
+        FlushRegion(0x0, 0xFFFFFFFF);
+    }
+    // Unmark all of the marked pages
+    for (auto& pair : RangeFromInterval(cached_pages, flush_interval)) {
+        const auto interval = pair.first & flush_interval;
+        const int count = pair.second;
+
+        const PAddr interval_start_addr = boost::icl::first(interval) << Memory::PAGE_BITS;
+        const PAddr interval_end_addr = boost::icl::last_next(interval) << Memory::PAGE_BITS;
+        const u32 interval_size = interval_end_addr - interval_start_addr;
+
+        VideoCore::g_memory->RasterizerMarkRegionCached(interval_start_addr, interval_size, false);
+    }
+
+    // Remove the whole cache without really looking at it.
+    cached_pages -= flush_interval;
+    dirty_regions -= SurfaceInterval(0x0, 0xFFFFFFFF);
+    surface_cache -= SurfaceInterval(0x0, 0xFFFFFFFF);
+    remove_surfaces.clear();
+}
+
 void RasterizerCacheOpenGL::FlushRegion(PAddr addr, u32 size, Surface flush_surface) {
     if (size == 0)
         return;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 24dd9f594..673beb449 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -280,6 +280,9 @@ public:
     /// Flush all cached resources tracked by this cache manager
     void FlushAll();
 
+    /// Clear all cached resources tracked by this cache manager
+    void ClearAll(bool flush);
+
 private:
     void DuplicateSurface(const Surface& src_surface, const Surface& dest_surface);
 
diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h
index bb6a5fae7..05a1e8b80 100644
--- a/src/video_core/shader/shader.h
+++ b/src/video_core/shader/shader.h
@@ -8,6 +8,9 @@
 #include <cstddef>
 #include <functional>
 #include <type_traits>
+#include <boost/serialization/access.hpp>
+#include <boost/serialization/array.hpp>
+#include <boost/serialization/base_object.hpp>
 #include <nihstro/shader_bytecode.h>
 #include "common/assert.h"
 #include "common/common_funcs.h"
@@ -31,6 +34,13 @@ using SwizzleData = std::array<u32, MAX_SWIZZLE_DATA_LENGTH>;
 
 struct AttributeBuffer {
     alignas(16) Common::Vec4<float24> attr[16];
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& attr;
+    }
 };
 
 /// Handler type for receiving vertex outputs from vertex shader or geometry shader
@@ -54,6 +64,20 @@ struct OutputVertex {
     static void ValidateSemantics(const RasterizerRegs& regs);
     static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs,
                                             const AttributeBuffer& output);
+
+private:
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int) {
+        ar& pos;
+        ar& quat;
+        ar& color;
+        ar& tc0;
+        ar& tc1;
+        ar& tc0_w;
+        ar& view;
+        ar& tc2;
+    }
+    friend class boost::serialization::access;
 };
 #define ASSERT_POS(var, pos)                                                                       \
     static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong "       \
@@ -90,6 +114,18 @@ struct GSEmitter {
     GSEmitter();
     ~GSEmitter();
     void Emit(Common::Vec4<float24> (&output_regs)[16]);
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& buffer;
+        ar& vertex_id;
+        ar& prim_emit;
+        ar& winding;
+        ar& output_mask;
+        // Handlers are ignored because they're constant
+    }
 };
 static_assert(std::is_standard_layout<GSEmitter>::value, "GSEmitter is not standard layout type");
 
@@ -107,6 +143,15 @@ struct UnitState {
         alignas(16) Common::Vec4<float24> input[16];
         alignas(16) Common::Vec4<float24> temporary[16];
         alignas(16) Common::Vec4<float24> output[16];
+
+    private:
+        friend class boost::serialization::access;
+        template <class Archive>
+        void serialize(Archive& ar, const unsigned int file_version) {
+            ar& input;
+            ar& temporary;
+            ar& output;
+        }
     } registers;
     static_assert(std::is_pod<Registers>::value, "Structure is not POD");
 
@@ -159,6 +204,16 @@ struct UnitState {
     void LoadInput(const ShaderRegs& config, const AttributeBuffer& input);
 
     void WriteOutput(const ShaderRegs& config, AttributeBuffer& output);
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& registers;
+        ar& conditional_code;
+        ar& address_registers;
+        // emitter_ptr is only set by GSUnitState and is serialized there
+    }
 };
 
 /**
@@ -172,6 +227,14 @@ struct GSUnitState : public UnitState {
     void ConfigOutput(const ShaderRegs& config);
 
     GSEmitter emitter;
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& boost::serialization::base_object<UnitState>(*this);
+        ar& emitter;
+    }
 };
 
 struct Uniforms {
@@ -193,6 +256,15 @@ struct Uniforms {
     static std::size_t GetIntUniformOffset(unsigned index) {
         return offsetof(Uniforms, i) + index * sizeof(Common::Vec4<u8>);
     }
+
+private:
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& f;
+        ar& b;
+        ar& i;
+    }
 };
 
 struct ShaderSetup {
@@ -237,6 +309,18 @@ private:
     bool swizzle_data_hash_dirty = true;
     u64 program_code_hash = 0xDEADC0DE;
     u64 swizzle_data_hash = 0xDEADC0DE;
+
+    friend class boost::serialization::access;
+    template <class Archive>
+    void serialize(Archive& ar, const unsigned int file_version) {
+        ar& uniforms;
+        ar& program_code;
+        ar& swizzle_data;
+        ar& program_code_hash_dirty;
+        ar& swizzle_data_hash_dirty;
+        ar& program_code_hash;
+        ar& swizzle_data_hash;
+    }
 };
 
 class ShaderEngine {
diff --git a/src/video_core/swrasterizer/swrasterizer.h b/src/video_core/swrasterizer/swrasterizer.h
index e2292f4a4..9e7a140f1 100644
--- a/src/video_core/swrasterizer/swrasterizer.h
+++ b/src/video_core/swrasterizer/swrasterizer.h
@@ -22,6 +22,7 @@ class SWRasterizer : public RasterizerInterface {
     void FlushRegion(PAddr addr, u32 size) override {}
     void InvalidateRegion(PAddr addr, u32 size) override {}
     void FlushAndInvalidateRegion(PAddr addr, u32 size) override {}
+    void ClearAll(bool flush) override {}
 };
 
 } // namespace VideoCore
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 1748efcc6..619ea3d4c 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -3,9 +3,11 @@
 // Refer to the license.txt file included.
 
 #include <memory>
+#include "common/archives.h"
 #include "common/logging/log.h"
 #include "core/settings.h"
 #include "video_core/pica.h"
+#include "video_core/pica_state.h"
 #include "video_core/renderer_base.h"
 #include "video_core/renderer_opengl/gl_vars.h"
 #include "video_core/renderer_opengl/renderer_opengl.h"
@@ -87,4 +89,11 @@ u16 GetResolutionScaleFactor() {
     }
 }
 
+template <class Archive>
+void serialize(Archive& ar, const unsigned int) {
+    ar& Pica::g_state;
+}
+
 } // namespace VideoCore
+
+SERIALIZE_IMPL(VideoCore)
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index 9951cd9a7..409f4deb2 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -5,6 +5,7 @@
 #pragma once
 
 #include <atomic>
+#include <iostream>
 #include <memory>
 #include "core/frontend/emu_window.h"
 
@@ -62,4 +63,7 @@ void RequestScreenshot(void* data, std::function<void()> callback,
 
 u16 GetResolutionScaleFactor();
 
+template <class Archive>
+void serialize(Archive& ar, const unsigned int file_version);
+
 } // namespace VideoCore