diff --git a/Source/Core/Common/x64Emitter.h b/Source/Core/Common/x64Emitter.h
index d027a1a9ef..6bf034b455 100644
--- a/Source/Core/Common/x64Emitter.h
+++ b/Source/Core/Common/x64Emitter.h
@@ -1137,6 +1137,17 @@ public:
     ABI_CallFunction(func);
   }
 
+  template <typename FunctionPointer>
+  void ABI_CallFunctionPAC(int bits, FunctionPointer func, const void* ptr1, const Gen::OpArg& arg2,
+                           u32 param3)
+  {
+    if (!arg2.IsSimpleReg(ABI_PARAM2))
+      MOV(bits, R(ABI_PARAM2), arg2);
+    MOV(32, R(ABI_PARAM3), Imm32(param3));
+    MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast<u64>(ptr1)));
+    ABI_CallFunction(func);
+  }
+
   template <typename FunctionPointer>
   void ABI_CallFunctionA(int bits, FunctionPointer func, const Gen::OpArg& arg1)
   {
diff --git a/Source/Core/Core/ActionReplay.cpp b/Source/Core/Core/ActionReplay.cpp
index 6d2f1c476e..05b87e11b4 100644
--- a/Source/Core/Core/ActionReplay.cpp
+++ b/Source/Core/Core/ActionReplay.cpp
@@ -375,7 +375,7 @@ static bool Subtype_RamWriteAndFill(const Core::CPUThreadGuard& guard, const ARA
     const u32 repeat = data >> 8;
     for (u32 i = 0; i <= repeat; ++i)
     {
-      PowerPC::HostWrite_U8(guard, data & 0xFF, new_addr + i);
+      PowerPC::MMU::HostWrite_U8(guard, data & 0xFF, new_addr + i);
       LogInfo("Wrote {:08x} to address {:08x}", data & 0xFF, new_addr + i);
     }
     LogInfo("--------");
@@ -389,7 +389,7 @@ static bool Subtype_RamWriteAndFill(const Core::CPUThreadGuard& guard, const ARA
     const u32 repeat = data >> 16;
     for (u32 i = 0; i <= repeat; ++i)
     {
-      PowerPC::HostWrite_U16(guard, data & 0xFFFF, new_addr + i * 2);
+      PowerPC::MMU::HostWrite_U16(guard, data & 0xFFFF, new_addr + i * 2);
       LogInfo("Wrote {:08x} to address {:08x}", data & 0xFFFF, new_addr + i * 2);
     }
     LogInfo("--------");
@@ -400,7 +400,7 @@ static bool Subtype_RamWriteAndFill(const Core::CPUThreadGuard& guard, const ARA
   case DATATYPE_32BIT:  // Dword write
     LogInfo("32-bit Write");
     LogInfo("--------");
-    PowerPC::HostWrite_U32(guard, data, new_addr);
+    PowerPC::MMU::HostWrite_U32(guard, data, new_addr);
     LogInfo("Wrote {:08x} to address {:08x}", data, new_addr);
     LogInfo("--------");
     break;
@@ -420,7 +420,7 @@ static bool Subtype_WriteToPointer(const Core::CPUThreadGuard& guard, const ARAd
                                    const u32 data)
 {
   const u32 new_addr = addr.GCAddress();
-  const u32 ptr = PowerPC::HostRead_U32(guard, new_addr);
+  const u32 ptr = PowerPC::MMU::HostRead_U32(guard, new_addr);
 
   LogInfo("Hardware Address: {:08x}", new_addr);
   LogInfo("Size: {:08x}", addr.size);
@@ -436,7 +436,7 @@ static bool Subtype_WriteToPointer(const Core::CPUThreadGuard& guard, const ARAd
     LogInfo("Pointer: {:08x}", ptr);
     LogInfo("Byte: {:08x}", thebyte);
     LogInfo("Offset: {:08x}", offset);
-    PowerPC::HostWrite_U8(guard, thebyte, ptr + offset);
+    PowerPC::MMU::HostWrite_U8(guard, thebyte, ptr + offset);
     LogInfo("Wrote {:08x} to address {:08x}", thebyte, ptr + offset);
     LogInfo("--------");
     break;
@@ -451,7 +451,7 @@ static bool Subtype_WriteToPointer(const Core::CPUThreadGuard& guard, const ARAd
     LogInfo("Pointer: {:08x}", ptr);
     LogInfo("Byte: {:08x}", theshort);
     LogInfo("Offset: {:08x}", offset);
-    PowerPC::HostWrite_U16(guard, theshort, ptr + offset);
+    PowerPC::MMU::HostWrite_U16(guard, theshort, ptr + offset);
     LogInfo("Wrote {:08x} to address {:08x}", theshort, ptr + offset);
     LogInfo("--------");
     break;
@@ -461,7 +461,7 @@ static bool Subtype_WriteToPointer(const Core::CPUThreadGuard& guard, const ARAd
   case DATATYPE_32BIT:
     LogInfo("Write 32-bit to pointer");
     LogInfo("--------");
-    PowerPC::HostWrite_U32(guard, data, ptr);
+    PowerPC::MMU::HostWrite_U32(guard, data, ptr);
     LogInfo("Wrote {:08x} to address {:08x}", data, ptr);
     LogInfo("--------");
     break;
@@ -489,24 +489,28 @@ static bool Subtype_AddCode(const Core::CPUThreadGuard& guard, const ARAddr& add
   case DATATYPE_8BIT:
     LogInfo("8-bit Add");
     LogInfo("--------");
-    PowerPC::HostWrite_U8(guard, PowerPC::HostRead_U8(guard, new_addr) + data, new_addr);
-    LogInfo("Wrote {:02x} to address {:08x}", PowerPC::HostRead_U8(guard, new_addr), new_addr);
+    PowerPC::MMU::HostWrite_U8(guard, PowerPC::MMU::HostRead_U8(guard, new_addr) + data, new_addr);
+    LogInfo("Wrote {:02x} to address {:08x}", PowerPC::MMU::HostRead_U8(guard, new_addr), new_addr);
     LogInfo("--------");
     break;
 
   case DATATYPE_16BIT:
     LogInfo("16-bit Add");
     LogInfo("--------");
-    PowerPC::HostWrite_U16(guard, PowerPC::HostRead_U16(guard, new_addr) + data, new_addr);
-    LogInfo("Wrote {:04x} to address {:08x}", PowerPC::HostRead_U16(guard, new_addr), new_addr);
+    PowerPC::MMU::HostWrite_U16(guard, PowerPC::MMU::HostRead_U16(guard, new_addr) + data,
+                                new_addr);
+    LogInfo("Wrote {:04x} to address {:08x}", PowerPC::MMU::HostRead_U16(guard, new_addr),
+            new_addr);
     LogInfo("--------");
     break;
 
   case DATATYPE_32BIT:
     LogInfo("32-bit Add");
     LogInfo("--------");
-    PowerPC::HostWrite_U32(guard, PowerPC::HostRead_U32(guard, new_addr) + data, new_addr);
-    LogInfo("Wrote {:08x} to address {:08x}", PowerPC::HostRead_U32(guard, new_addr), new_addr);
+    PowerPC::MMU::HostWrite_U32(guard, PowerPC::MMU::HostRead_U32(guard, new_addr) + data,
+                                new_addr);
+    LogInfo("Wrote {:08x} to address {:08x}", PowerPC::MMU::HostRead_U32(guard, new_addr),
+            new_addr);
     LogInfo("--------");
     break;
 
@@ -515,12 +519,12 @@ static bool Subtype_AddCode(const Core::CPUThreadGuard& guard, const ARAddr& add
     LogInfo("32-bit floating Add");
     LogInfo("--------");
 
-    const u32 read = PowerPC::HostRead_U32(guard, new_addr);
+    const u32 read = PowerPC::MMU::HostRead_U32(guard, new_addr);
     const float read_float = Common::BitCast<float>(read);
     // data contains an (unsigned?) integer value
     const float fread = read_float + static_cast<float>(data);
     const u32 newval = Common::BitCast<u32>(fread);
-    PowerPC::HostWrite_U32(guard, newval, new_addr);
+    PowerPC::MMU::HostWrite_U32(guard, newval, new_addr);
     LogInfo("Old Value {:08x}", read);
     LogInfo("Increment {:08x}", data);
     LogInfo("New value {:08x}", newval);
@@ -578,7 +582,7 @@ static bool ZeroCode_FillAndSlide(const Core::CPUThreadGuard& guard, const u32 v
     LogInfo("--------");
     for (int i = 0; i < write_num; ++i)
     {
-      PowerPC::HostWrite_U8(guard, val & 0xFF, curr_addr);
+      PowerPC::MMU::HostWrite_U8(guard, val & 0xFF, curr_addr);
       curr_addr += addr_incr;
       val += val_incr;
       LogInfo("Write {:08x} to address {:08x}", val & 0xFF, curr_addr);
@@ -594,7 +598,7 @@ static bool ZeroCode_FillAndSlide(const Core::CPUThreadGuard& guard, const u32 v
     LogInfo("--------");
     for (int i = 0; i < write_num; ++i)
     {
-      PowerPC::HostWrite_U16(guard, val & 0xFFFF, curr_addr);
+      PowerPC::MMU::HostWrite_U16(guard, val & 0xFFFF, curr_addr);
       LogInfo("Write {:08x} to address {:08x}", val & 0xFFFF, curr_addr);
       curr_addr += addr_incr * 2;
       val += val_incr;
@@ -609,7 +613,7 @@ static bool ZeroCode_FillAndSlide(const Core::CPUThreadGuard& guard, const u32 v
     LogInfo("--------");
     for (int i = 0; i < write_num; ++i)
     {
-      PowerPC::HostWrite_U32(guard, val, curr_addr);
+      PowerPC::MMU::HostWrite_U32(guard, val, curr_addr);
       LogInfo("Write {:08x} to address {:08x}", val, curr_addr);
       curr_addr += addr_incr * 4;
       val += val_incr;
@@ -650,14 +654,15 @@ static bool ZeroCode_MemoryCopy(const Core::CPUThreadGuard& guard, const u32 val
     {  // Memory Copy With Pointers Support
       LogInfo("Memory Copy With Pointers Support");
       LogInfo("--------");
-      const u32 ptr_dest = PowerPC::HostRead_U32(guard, addr_dest);
+      const u32 ptr_dest = PowerPC::MMU::HostRead_U32(guard, addr_dest);
       LogInfo("Resolved Dest Address to: {:08x}", ptr_dest);
-      const u32 ptr_src = PowerPC::HostRead_U32(guard, addr_src);
+      const u32 ptr_src = PowerPC::MMU::HostRead_U32(guard, addr_src);
       LogInfo("Resolved Src Address to: {:08x}", ptr_src);
       for (int i = 0; i < num_bytes; ++i)
       {
-        PowerPC::HostWrite_U8(guard, PowerPC::HostRead_U8(guard, ptr_src + i), ptr_dest + i);
-        LogInfo("Wrote {:08x} to address {:08x}", PowerPC::HostRead_U8(guard, ptr_src + i),
+        PowerPC::MMU::HostWrite_U8(guard, PowerPC::MMU::HostRead_U8(guard, ptr_src + i),
+                                   ptr_dest + i);
+        LogInfo("Wrote {:08x} to address {:08x}", PowerPC::MMU::HostRead_U8(guard, ptr_src + i),
                 ptr_dest + i);
       }
       LogInfo("--------");
@@ -668,8 +673,9 @@ static bool ZeroCode_MemoryCopy(const Core::CPUThreadGuard& guard, const u32 val
       LogInfo("--------");
       for (int i = 0; i < num_bytes; ++i)
       {
-        PowerPC::HostWrite_U8(guard, PowerPC::HostRead_U8(guard, addr_src + i), addr_dest + i);
-        LogInfo("Wrote {:08x} to address {:08x}", PowerPC::HostRead_U8(guard, addr_src + i),
+        PowerPC::MMU::HostWrite_U8(guard, PowerPC::MMU::HostRead_U8(guard, addr_src + i),
+                                   addr_dest + i);
+        LogInfo("Wrote {:08x} to address {:08x}", PowerPC::MMU::HostRead_U8(guard, addr_src + i),
                 addr_dest + i);
       }
       LogInfo("--------");
@@ -777,16 +783,16 @@ static bool ConditionalCode(const Core::CPUThreadGuard& guard, const ARAddr& add
   switch (addr.size)
   {
   case DATATYPE_8BIT:
-    result = CompareValues(PowerPC::HostRead_U8(guard, new_addr), (data & 0xFF), addr.type);
+    result = CompareValues(PowerPC::MMU::HostRead_U8(guard, new_addr), (data & 0xFF), addr.type);
     break;
 
   case DATATYPE_16BIT:
-    result = CompareValues(PowerPC::HostRead_U16(guard, new_addr), (data & 0xFFFF), addr.type);
+    result = CompareValues(PowerPC::MMU::HostRead_U16(guard, new_addr), (data & 0xFFFF), addr.type);
     break;
 
   case DATATYPE_32BIT_FLOAT:
   case DATATYPE_32BIT:
-    result = CompareValues(PowerPC::HostRead_U32(guard, new_addr), data, addr.type);
+    result = CompareValues(PowerPC::MMU::HostRead_U32(guard, new_addr), data, addr.type);
     break;
 
   default:
diff --git a/Source/Core/Core/Boot/Boot_BS2Emu.cpp b/Source/Core/Core/Boot/Boot_BS2Emu.cpp
index 222490d95c..be799ab1a2 100644
--- a/Source/Core/Core/Boot/Boot_BS2Emu.cpp
+++ b/Source/Core/Core/Boot/Boot_BS2Emu.cpp
@@ -51,7 +51,7 @@ void PresetTimeBaseTicks(Core::System& system, const Core::CPUThreadGuard& guard
 
   const u64 time_base_ticks = emulated_time * 40500000ULL;
 
-  PowerPC::HostWrite_U64(guard, time_base_ticks, 0x800030D8);
+  PowerPC::MMU::HostWrite_U64(guard, time_base_ticks, 0x800030D8);
 }
 }  // Anonymous namespace
 
@@ -127,8 +127,10 @@ void CBoot::SetupBAT(Core::System& system, bool is_wii)
     ppc_state.spr[SPR_DBAT5L] = 0x1000002a;
     HID4(ppc_state).SBE = 1;
   }
-  PowerPC::DBATUpdated();
-  PowerPC::IBATUpdated();
+
+  auto& mmu = system.GetMMU();
+  mmu.DBATUpdated();
+  mmu.IBATUpdated();
 }
 
 bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard, bool is_wii,
@@ -153,6 +155,7 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
   // TODO - Make Apploader(or just RunFunction()) debuggable!!!
 
   auto& ppc_state = system.GetPPCState();
+  auto& mmu = system.GetMMU();
 
   // Call iAppLoaderEntry.
   DEBUG_LOG_FMT(BOOT, "Call iAppLoaderEntry");
@@ -161,14 +164,14 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
   ppc_state.gpr[4] = iAppLoaderFuncAddr + 4;
   ppc_state.gpr[5] = iAppLoaderFuncAddr + 8;
   RunFunction(system, *entry);
-  const u32 iAppLoaderInit = PowerPC::Read_U32(iAppLoaderFuncAddr + 0);
-  const u32 iAppLoaderMain = PowerPC::Read_U32(iAppLoaderFuncAddr + 4);
-  const u32 iAppLoaderClose = PowerPC::Read_U32(iAppLoaderFuncAddr + 8);
+  const u32 iAppLoaderInit = mmu.Read_U32(iAppLoaderFuncAddr + 0);
+  const u32 iAppLoaderMain = mmu.Read_U32(iAppLoaderFuncAddr + 4);
+  const u32 iAppLoaderClose = mmu.Read_U32(iAppLoaderFuncAddr + 8);
 
   // iAppLoaderInit
   DEBUG_LOG_FMT(BOOT, "Call iAppLoaderInit");
-  PowerPC::HostWrite_U32(guard, 0x4E800020, 0x81300000);  // Write BLR
-  HLE::Patch(system, 0x81300000, "AppLoaderReport");      // HLE OSReport for Apploader
+  PowerPC::MMU::HostWrite_U32(guard, 0x4E800020, 0x81300000);  // Write BLR
+  HLE::Patch(system, 0x81300000, "AppLoaderReport");           // HLE OSReport for Apploader
   ppc_state.gpr[3] = 0x81300000;
   RunFunction(system, iAppLoaderInit);
 
@@ -189,9 +192,9 @@ bool CBoot::RunApploader(Core::System& system, const Core::CPUThreadGuard& guard
   // iAppLoaderMain returns 0 when there are no more sections to copy.
   while (ppc_state.gpr[3] != 0x00)
   {
-    const u32 ram_address = PowerPC::Read_U32(0x81300004);
-    const u32 length = PowerPC::Read_U32(0x81300008);
-    const u32 dvd_offset = PowerPC::Read_U32(0x8130000c) << (is_wii ? 2 : 0);
+    const u32 ram_address = mmu.Read_U32(0x81300004);
+    const u32 length = mmu.Read_U32(0x81300008);
+    const u32 dvd_offset = mmu.Read_U32(0x8130000c) << (is_wii ? 2 : 0);
 
     INFO_LOG_FMT(BOOT, "DVDRead: offset: {:08x}   memOffset: {:08x}   length: {}", dvd_offset,
                  ram_address, length);
@@ -223,35 +226,35 @@ void CBoot::SetupGCMemory(Core::System& system, const Core::CPUThreadGuard& guar
   auto& memory = system.GetMemory();
 
   // Booted from bootrom. 0xE5207C22 = booted from jtag
-  PowerPC::HostWrite_U32(guard, 0x0D15EA5E, 0x80000020);
+  PowerPC::MMU::HostWrite_U32(guard, 0x0D15EA5E, 0x80000020);
 
   // Physical Memory Size (24MB on retail)
-  PowerPC::HostWrite_U32(guard, memory.GetRamSizeReal(), 0x80000028);
+  PowerPC::MMU::HostWrite_U32(guard, memory.GetRamSizeReal(), 0x80000028);
 
   // Console type - DevKit  (retail ID == 0x00000003) see YAGCD 4.2.1.1.2
   // TODO: determine why some games fail when using a retail ID.
   // (Seem to take different EXI paths, see Ikaruga for example)
   const u32 console_type = static_cast<u32>(Core::ConsoleType::LatestDevkit);
-  PowerPC::HostWrite_U32(guard, console_type, 0x8000002C);
+  PowerPC::MMU::HostWrite_U32(guard, console_type, 0x8000002C);
 
   // Fake the VI Init of the IPL (YAGCD 4.2.1.4)
-  PowerPC::HostWrite_U32(guard, DiscIO::IsNTSC(SConfig::GetInstance().m_region) ? 0 : 1,
-                         0x800000CC);
+  PowerPC::MMU::HostWrite_U32(guard, DiscIO::IsNTSC(SConfig::GetInstance().m_region) ? 0 : 1,
+                              0x800000CC);
 
   // ARAM Size. 16MB main + 4/16/32MB external. (retail consoles have no external ARAM)
-  PowerPC::HostWrite_U32(guard, 0x01000000, 0x800000d0);
+  PowerPC::MMU::HostWrite_U32(guard, 0x01000000, 0x800000d0);
 
-  PowerPC::HostWrite_U32(guard, 0x09a7ec80, 0x800000F8);  // Bus Clock Speed
-  PowerPC::HostWrite_U32(guard, 0x1cf7c580, 0x800000FC);  // CPU Clock Speed
+  PowerPC::MMU::HostWrite_U32(guard, 0x09a7ec80, 0x800000F8);  // Bus Clock Speed
+  PowerPC::MMU::HostWrite_U32(guard, 0x1cf7c580, 0x800000FC);  // CPU Clock Speed
 
-  PowerPC::HostWrite_U32(guard, 0x4c000064, 0x80000300);  // Write default DSI Handler:     rfi
-  PowerPC::HostWrite_U32(guard, 0x4c000064, 0x80000800);  // Write default FPU Handler:     rfi
-  PowerPC::HostWrite_U32(guard, 0x4c000064, 0x80000C00);  // Write default Syscall Handler: rfi
+  PowerPC::MMU::HostWrite_U32(guard, 0x4c000064, 0x80000300);  // Write default DSI Handler:     rfi
+  PowerPC::MMU::HostWrite_U32(guard, 0x4c000064, 0x80000800);  // Write default FPU Handler:     rfi
+  PowerPC::MMU::HostWrite_U32(guard, 0x4c000064, 0x80000C00);  // Write default Syscall Handler: rfi
 
   PresetTimeBaseTicks(system, guard);
 
   // HIO checks this
-  // PowerPC::HostWrite_U16(0x8200, 0x000030e6);   // Console type
+  // PowerPC::MMU::HostWrite_U16(0x8200, 0x000030e6);   // Console type
 }
 
 // __________________________________________________________________________________________________
diff --git a/Source/Core/Core/CheatSearch.cpp b/Source/Core/Core/CheatSearch.cpp
index 8fbfd86c14..2f75998ae8 100644
--- a/Source/Core/Core/CheatSearch.cpp
+++ b/Source/Core/Core/CheatSearch.cpp
@@ -111,7 +111,7 @@ std::optional<PowerPC::ReadResult<u8>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  return PowerPC::HostTryReadU8(guard, addr, space);
+  return PowerPC::MMU::HostTryReadU8(guard, addr, space);
 }
 
 template <>
@@ -119,7 +119,7 @@ std::optional<PowerPC::ReadResult<u16>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  return PowerPC::HostTryReadU16(guard, addr, space);
+  return PowerPC::MMU::HostTryReadU16(guard, addr, space);
 }
 
 template <>
@@ -127,7 +127,7 @@ std::optional<PowerPC::ReadResult<u32>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  return PowerPC::HostTryReadU32(guard, addr, space);
+  return PowerPC::MMU::HostTryReadU32(guard, addr, space);
 }
 
 template <>
@@ -135,7 +135,7 @@ std::optional<PowerPC::ReadResult<u64>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  return PowerPC::HostTryReadU64(guard, addr, space);
+  return PowerPC::MMU::HostTryReadU64(guard, addr, space);
 }
 
 template <>
@@ -143,7 +143,7 @@ std::optional<PowerPC::ReadResult<s8>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  auto tmp = PowerPC::HostTryReadU8(guard, addr, space);
+  auto tmp = PowerPC::MMU::HostTryReadU8(guard, addr, space);
   if (!tmp)
     return std::nullopt;
   return PowerPC::ReadResult<s8>(tmp->translated, Common::BitCast<s8>(tmp->value));
@@ -154,7 +154,7 @@ std::optional<PowerPC::ReadResult<s16>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  auto tmp = PowerPC::HostTryReadU16(guard, addr, space);
+  auto tmp = PowerPC::MMU::HostTryReadU16(guard, addr, space);
   if (!tmp)
     return std::nullopt;
   return PowerPC::ReadResult<s16>(tmp->translated, Common::BitCast<s16>(tmp->value));
@@ -165,7 +165,7 @@ std::optional<PowerPC::ReadResult<s32>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  auto tmp = PowerPC::HostTryReadU32(guard, addr, space);
+  auto tmp = PowerPC::MMU::HostTryReadU32(guard, addr, space);
   if (!tmp)
     return std::nullopt;
   return PowerPC::ReadResult<s32>(tmp->translated, Common::BitCast<s32>(tmp->value));
@@ -176,7 +176,7 @@ std::optional<PowerPC::ReadResult<s64>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  auto tmp = PowerPC::HostTryReadU64(guard, addr, space);
+  auto tmp = PowerPC::MMU::HostTryReadU64(guard, addr, space);
   if (!tmp)
     return std::nullopt;
   return PowerPC::ReadResult<s64>(tmp->translated, Common::BitCast<s64>(tmp->value));
@@ -187,7 +187,7 @@ std::optional<PowerPC::ReadResult<float>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  return PowerPC::HostTryReadF32(guard, addr, space);
+  return PowerPC::MMU::HostTryReadF32(guard, addr, space);
 }
 
 template <>
@@ -195,7 +195,7 @@ std::optional<PowerPC::ReadResult<double>>
 TryReadValueFromEmulatedMemory(const Core::CPUThreadGuard& guard, u32 addr,
                                PowerPC::RequestedAddressSpace space)
 {
-  return PowerPC::HostTryReadF64(guard, addr, space);
+  return PowerPC::MMU::HostTryReadF64(guard, addr, space);
 }
 }  // namespace
 
diff --git a/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp b/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp
index 023c004897..cab601c83d 100644
--- a/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp
+++ b/Source/Core/Core/Debugger/Debugger_SymbolMap.cpp
@@ -41,7 +41,7 @@ void AddAutoBreakpoints()
 // Returns true if the address is not a valid RAM address or NULL.
 static bool IsStackBottom(const Core::CPUThreadGuard& guard, u32 addr)
 {
-  return !addr || !PowerPC::HostIsRAMAddress(guard, addr);
+  return !addr || !PowerPC::MMU::HostIsRAMAddress(guard, addr);
 }
 
 static void WalkTheStack(Core::System& system, const Core::CPUThreadGuard& guard,
@@ -51,18 +51,18 @@ static void WalkTheStack(Core::System& system, const Core::CPUThreadGuard& guard
 
   if (!IsStackBottom(guard, ppc_state.gpr[1]))
   {
-    u32 addr = PowerPC::HostRead_U32(guard, ppc_state.gpr[1]);  // SP
+    u32 addr = PowerPC::MMU::HostRead_U32(guard, ppc_state.gpr[1]);  // SP
 
     // Walk the stack chain
     for (int count = 0; !IsStackBottom(guard, addr + 4) && (count++ < 20); ++count)
     {
-      u32 func_addr = PowerPC::HostRead_U32(guard, addr + 4);
+      u32 func_addr = PowerPC::MMU::HostRead_U32(guard, addr + 4);
       stack_step(func_addr);
 
       if (IsStackBottom(guard, addr))
         break;
 
-      addr = PowerPC::HostRead_U32(guard, addr);
+      addr = PowerPC::MMU::HostRead_U32(guard, addr);
     }
   }
 }
@@ -75,7 +75,7 @@ bool GetCallstack(Core::System& system, const Core::CPUThreadGuard& guard,
 {
   auto& ppc_state = system.GetPPCState();
 
-  if (!Core::IsRunning() || !PowerPC::HostIsRAMAddress(guard, ppc_state.gpr[1]))
+  if (!Core::IsRunning() || !PowerPC::MMU::HostIsRAMAddress(guard, ppc_state.gpr[1]))
     return false;
 
   if (LR(ppc_state) == 0)
diff --git a/Source/Core/Core/Debugger/OSThread.cpp b/Source/Core/Core/Debugger/OSThread.cpp
index b8f3e25b6d..d4cee37d26 100644
--- a/Source/Core/Core/Debugger/OSThread.cpp
+++ b/Source/Core/Core/Debugger/OSThread.cpp
@@ -19,23 +19,23 @@ namespace Core::Debug
 void OSContext::Read(const Core::CPUThreadGuard& guard, u32 addr)
 {
   for (std::size_t i = 0; i < gpr.size(); i++)
-    gpr[i] = PowerPC::HostRead_U32(guard, addr + u32(i * sizeof(int)));
-  cr = PowerPC::HostRead_U32(guard, addr + 0x80);
-  lr = PowerPC::HostRead_U32(guard, addr + 0x84);
-  ctr = PowerPC::HostRead_U32(guard, addr + 0x88);
-  xer = PowerPC::HostRead_U32(guard, addr + 0x8C);
+    gpr[i] = PowerPC::MMU::HostRead_U32(guard, addr + u32(i * sizeof(int)));
+  cr = PowerPC::MMU::HostRead_U32(guard, addr + 0x80);
+  lr = PowerPC::MMU::HostRead_U32(guard, addr + 0x84);
+  ctr = PowerPC::MMU::HostRead_U32(guard, addr + 0x88);
+  xer = PowerPC::MMU::HostRead_U32(guard, addr + 0x8C);
   for (std::size_t i = 0; i < fpr.size(); i++)
-    fpr[i] = PowerPC::HostRead_F64(guard, addr + 0x90 + u32(i * sizeof(double)));
-  fpscr = PowerPC::HostRead_U64(guard, addr + 0x190);
-  srr0 = PowerPC::HostRead_U32(guard, addr + 0x198);
-  srr1 = PowerPC::HostRead_U32(guard, addr + 0x19c);
-  dummy = PowerPC::HostRead_U16(guard, addr + 0x1a0);
-  state = static_cast<OSContext::State>(PowerPC::HostRead_U16(guard, addr + 0x1a2));
+    fpr[i] = PowerPC::MMU::HostRead_F64(guard, addr + 0x90 + u32(i * sizeof(double)));
+  fpscr = PowerPC::MMU::HostRead_U64(guard, addr + 0x190);
+  srr0 = PowerPC::MMU::HostRead_U32(guard, addr + 0x198);
+  srr1 = PowerPC::MMU::HostRead_U32(guard, addr + 0x19c);
+  dummy = PowerPC::MMU::HostRead_U16(guard, addr + 0x1a0);
+  state = static_cast<OSContext::State>(PowerPC::MMU::HostRead_U16(guard, addr + 0x1a2));
   for (std::size_t i = 0; i < gqr.size(); i++)
-    gqr[i] = PowerPC::HostRead_U32(guard, addr + 0x1a4 + u32(i * sizeof(int)));
+    gqr[i] = PowerPC::MMU::HostRead_U32(guard, addr + 0x1a4 + u32(i * sizeof(int)));
   psf_padding = 0;
   for (std::size_t i = 0; i < psf.size(); i++)
-    psf[i] = PowerPC::HostRead_F64(guard, addr + 0x1c8 + u32(i * sizeof(double)));
+    psf[i] = PowerPC::MMU::HostRead_F64(guard, addr + 0x1c8 + u32(i * sizeof(double)));
 }
 
 // Mutex offsets based on the following functions:
@@ -44,12 +44,12 @@ void OSContext::Read(const Core::CPUThreadGuard& guard, u32 addr)
 //  - __OSUnlockAllMutex
 void OSMutex::Read(const Core::CPUThreadGuard& guard, u32 addr)
 {
-  thread_queue.head = PowerPC::HostRead_U32(guard, addr);
-  thread_queue.tail = PowerPC::HostRead_U32(guard, addr + 0x4);
-  owner_addr = PowerPC::HostRead_U32(guard, addr + 0x8);
-  lock_count = PowerPC::HostRead_U32(guard, addr + 0xc);
-  link.next = PowerPC::HostRead_U32(guard, addr + 0x10);
-  link.prev = PowerPC::HostRead_U32(guard, addr + 0x14);
+  thread_queue.head = PowerPC::MMU::HostRead_U32(guard, addr);
+  thread_queue.tail = PowerPC::MMU::HostRead_U32(guard, addr + 0x4);
+  owner_addr = PowerPC::MMU::HostRead_U32(guard, addr + 0x8);
+  lock_count = PowerPC::MMU::HostRead_U32(guard, addr + 0xc);
+  link.next = PowerPC::MMU::HostRead_U32(guard, addr + 0x10);
+  link.prev = PowerPC::MMU::HostRead_U32(guard, addr + 0x14);
 }
 
 // Thread offsets based on the following functions:
@@ -67,38 +67,38 @@ void OSMutex::Read(const Core::CPUThreadGuard& guard, u32 addr)
 void OSThread::Read(const Core::CPUThreadGuard& guard, u32 addr)
 {
   context.Read(guard, addr);
-  state = PowerPC::HostRead_U16(guard, addr + 0x2c8);
-  is_detached = PowerPC::HostRead_U16(guard, addr + 0x2ca);
-  suspend = PowerPC::HostRead_U32(guard, addr + 0x2cc);
-  effective_priority = PowerPC::HostRead_U32(guard, addr + 0x2d0);
-  base_priority = PowerPC::HostRead_U32(guard, addr + 0x2d4);
-  exit_code_addr = PowerPC::HostRead_U32(guard, addr + 0x2d8);
+  state = PowerPC::MMU::HostRead_U16(guard, addr + 0x2c8);
+  is_detached = PowerPC::MMU::HostRead_U16(guard, addr + 0x2ca);
+  suspend = PowerPC::MMU::HostRead_U32(guard, addr + 0x2cc);
+  effective_priority = PowerPC::MMU::HostRead_U32(guard, addr + 0x2d0);
+  base_priority = PowerPC::MMU::HostRead_U32(guard, addr + 0x2d4);
+  exit_code_addr = PowerPC::MMU::HostRead_U32(guard, addr + 0x2d8);
 
-  queue_addr = PowerPC::HostRead_U32(guard, addr + 0x2dc);
-  queue_link.next = PowerPC::HostRead_U32(guard, addr + 0x2e0);
-  queue_link.prev = PowerPC::HostRead_U32(guard, addr + 0x2e4);
+  queue_addr = PowerPC::MMU::HostRead_U32(guard, addr + 0x2dc);
+  queue_link.next = PowerPC::MMU::HostRead_U32(guard, addr + 0x2e0);
+  queue_link.prev = PowerPC::MMU::HostRead_U32(guard, addr + 0x2e4);
 
-  join_queue.head = PowerPC::HostRead_U32(guard, addr + 0x2e8);
-  join_queue.tail = PowerPC::HostRead_U32(guard, addr + 0x2ec);
+  join_queue.head = PowerPC::MMU::HostRead_U32(guard, addr + 0x2e8);
+  join_queue.tail = PowerPC::MMU::HostRead_U32(guard, addr + 0x2ec);
 
-  mutex_addr = PowerPC::HostRead_U32(guard, addr + 0x2f0);
-  mutex_queue.head = PowerPC::HostRead_U32(guard, addr + 0x2f4);
-  mutex_queue.tail = PowerPC::HostRead_U32(guard, addr + 0x2f8);
+  mutex_addr = PowerPC::MMU::HostRead_U32(guard, addr + 0x2f0);
+  mutex_queue.head = PowerPC::MMU::HostRead_U32(guard, addr + 0x2f4);
+  mutex_queue.tail = PowerPC::MMU::HostRead_U32(guard, addr + 0x2f8);
 
-  thread_link.next = PowerPC::HostRead_U32(guard, addr + 0x2fc);
-  thread_link.prev = PowerPC::HostRead_U32(guard, addr + 0x300);
+  thread_link.next = PowerPC::MMU::HostRead_U32(guard, addr + 0x2fc);
+  thread_link.prev = PowerPC::MMU::HostRead_U32(guard, addr + 0x300);
 
-  stack_addr = PowerPC::HostRead_U32(guard, addr + 0x304);
-  stack_end = PowerPC::HostRead_U32(guard, addr + 0x308);
-  error = PowerPC::HostRead_U32(guard, addr + 0x30c);
-  specific[0] = PowerPC::HostRead_U32(guard, addr + 0x310);
-  specific[1] = PowerPC::HostRead_U32(guard, addr + 0x314);
+  stack_addr = PowerPC::MMU::HostRead_U32(guard, addr + 0x304);
+  stack_end = PowerPC::MMU::HostRead_U32(guard, addr + 0x308);
+  error = PowerPC::MMU::HostRead_U32(guard, addr + 0x30c);
+  specific[0] = PowerPC::MMU::HostRead_U32(guard, addr + 0x310);
+  specific[1] = PowerPC::MMU::HostRead_U32(guard, addr + 0x314);
 }
 
 bool OSThread::IsValid(const Core::CPUThreadGuard& guard) const
 {
-  return PowerPC::HostIsRAMAddress(guard, stack_end) &&
-         PowerPC::HostRead_U32(guard, stack_end) == STACK_MAGIC;
+  return PowerPC::MMU::HostIsRAMAddress(guard, stack_end) &&
+         PowerPC::MMU::HostRead_U32(guard, stack_end) == STACK_MAGIC;
 }
 
 OSThreadView::OSThreadView(const Core::CPUThreadGuard& guard, u32 addr)
@@ -192,9 +192,9 @@ std::string OSThreadView::GetSpecific(const Core::CPUThreadGuard& guard) const
 
   for (u32 addr : m_thread.specific)
   {
-    if (!PowerPC::HostIsRAMAddress(guard, addr))
+    if (!PowerPC::MMU::HostIsRAMAddress(guard, addr))
       break;
-    specific += fmt::format("{:08x} \"{}\"\n", addr, PowerPC::HostGetString(guard, addr));
+    specific += fmt::format("{:08x} \"{}\"\n", addr, PowerPC::MMU::HostGetString(guard, addr));
   }
 
   return specific;
diff --git a/Source/Core/Core/Debugger/PPCDebugInterface.cpp b/Source/Core/Core/Debugger/PPCDebugInterface.cpp
index d5311ee936..6d363fd6bf 100644
--- a/Source/Core/Core/Debugger/PPCDebugInterface.cpp
+++ b/Source/Core/Core/Debugger/PPCDebugInterface.cpp
@@ -33,20 +33,20 @@ void ApplyMemoryPatch(const Core::CPUThreadGuard& guard, Common::Debug::MemoryPa
 
   const u32 address = patch.address;
   const std::size_t size = patch.value.size();
-  if (!PowerPC::HostIsRAMAddress(guard, address))
+  if (!PowerPC::MMU::HostIsRAMAddress(guard, address))
     return;
 
   for (u32 offset = 0; offset < size; ++offset)
   {
     if (store_existing_value)
     {
-      const u8 value = PowerPC::HostRead_U8(guard, address + offset);
-      PowerPC::HostWrite_U8(guard, patch.value[offset], address + offset);
+      const u8 value = PowerPC::MMU::HostRead_U8(guard, address + offset);
+      PowerPC::MMU::HostWrite_U8(guard, patch.value[offset], address + offset);
       patch.value[offset] = value;
     }
     else
     {
-      PowerPC::HostWrite_U8(guard, patch.value[offset], address + offset);
+      PowerPC::MMU::HostWrite_U8(guard, patch.value[offset], address + offset);
     }
 
     if (((address + offset) % 4) == 3)
@@ -231,10 +231,10 @@ Common::Debug::Threads PPCDebugInterface::GetThreads(const Core::CPUThreadGuard&
   Common::Debug::Threads threads;
 
   constexpr u32 ACTIVE_QUEUE_HEAD_ADDR = 0x800000dc;
-  if (!PowerPC::HostIsRAMAddress(guard, ACTIVE_QUEUE_HEAD_ADDR))
+  if (!PowerPC::MMU::HostIsRAMAddress(guard, ACTIVE_QUEUE_HEAD_ADDR))
     return threads;
-  const u32 active_queue_head = PowerPC::HostRead_U32(guard, ACTIVE_QUEUE_HEAD_ADDR);
-  if (!PowerPC::HostIsRAMAddress(guard, active_queue_head))
+  const u32 active_queue_head = PowerPC::MMU::HostRead_U32(guard, ACTIVE_QUEUE_HEAD_ADDR);
+  if (!PowerPC::MMU::HostIsRAMAddress(guard, active_queue_head))
     return threads;
 
   auto active_thread = std::make_unique<Core::Debug::OSThreadView>(guard, active_queue_head);
@@ -243,7 +243,7 @@ Common::Debug::Threads PPCDebugInterface::GetThreads(const Core::CPUThreadGuard&
 
   std::vector<u32> visited_addrs{active_thread->GetAddress()};
   const auto insert_threads = [&guard, &threads, &visited_addrs](u32 addr, auto get_next_addr) {
-    while (addr != 0 && PowerPC::HostIsRAMAddress(guard, addr))
+    while (addr != 0 && PowerPC::MMU::HostIsRAMAddress(guard, addr))
     {
       if (std::find(visited_addrs.begin(), visited_addrs.end(), addr) != visited_addrs.end())
         break;
@@ -271,12 +271,12 @@ std::string PPCDebugInterface::Disassemble(const Core::CPUThreadGuard* guard, u3
 {
   if (guard)
   {
-    if (!PowerPC::HostIsRAMAddress(*guard, address))
+    if (!PowerPC::MMU::HostIsRAMAddress(*guard, address))
     {
       return "(No RAM here)";
     }
 
-    const u32 op = PowerPC::HostRead_Instruction(*guard, address);
+    const u32 op = PowerPC::MMU::HostRead_Instruction(*guard, address);
     std::string disasm = Common::GekkoDisassembler::Disassemble(op, address);
     const UGeckoInstruction inst{op};
 
@@ -300,7 +300,7 @@ std::string PPCDebugInterface::GetRawMemoryString(const Core::CPUThreadGuard& gu
   {
     const bool is_aram = memory != 0;
 
-    if (is_aram || PowerPC::HostIsRAMAddress(guard, address))
+    if (is_aram || PowerPC::MMU::HostIsRAMAddress(guard, address))
     {
       return fmt::format("{:08X}{}", ReadExtraMemory(guard, memory, address),
                          is_aram ? " (ARAM)" : "");
@@ -314,7 +314,7 @@ std::string PPCDebugInterface::GetRawMemoryString(const Core::CPUThreadGuard& gu
 
 u32 PPCDebugInterface::ReadMemory(const Core::CPUThreadGuard& guard, u32 address) const
 {
-  return PowerPC::HostRead_U32(guard, address);
+  return PowerPC::MMU::HostRead_U32(guard, address);
 }
 
 u32 PPCDebugInterface::ReadExtraMemory(const Core::CPUThreadGuard& guard, int memory,
@@ -323,7 +323,7 @@ u32 PPCDebugInterface::ReadExtraMemory(const Core::CPUThreadGuard& guard, int me
   switch (memory)
   {
   case 0:
-    return PowerPC::HostRead_U32(guard, address);
+    return PowerPC::MMU::HostRead_U32(guard, address);
   case 1:
   {
     auto& dsp = Core::System::GetInstance().GetDSP();
@@ -337,7 +337,7 @@ u32 PPCDebugInterface::ReadExtraMemory(const Core::CPUThreadGuard& guard, int me
 
 u32 PPCDebugInterface::ReadInstruction(const Core::CPUThreadGuard& guard, u32 address) const
 {
-  return PowerPC::HostRead_Instruction(guard, address);
+  return PowerPC::MMU::HostRead_Instruction(guard, address);
 }
 
 bool PPCDebugInterface::IsAlive() const
@@ -412,7 +412,7 @@ u32 PPCDebugInterface::GetColor(const Core::CPUThreadGuard* guard, u32 address)
 {
   if (!guard || !IsAlive())
     return 0xFFFFFF;
-  if (!PowerPC::HostIsRAMAddress(*guard, address))
+  if (!PowerPC::MMU::HostIsRAMAddress(*guard, address))
     return 0xeeeeee;
 
   Common::Symbol* symbol = g_symbolDB.GetSymbolFromAddr(address);
diff --git a/Source/Core/Core/Debugger/RSO.cpp b/Source/Core/Core/Debugger/RSO.cpp
index 319e1962c8..71070f21a9 100644
--- a/Source/Core/Core/Debugger/RSO.cpp
+++ b/Source/Core/Core/Debugger/RSO.cpp
@@ -17,37 +17,42 @@
 void RSOHeaderView::Load(const Core::CPUThreadGuard& guard, u32 address)
 {
   m_address = address;
-  m_header.entry.next_entry = PowerPC::HostRead_U32(guard, address);
-  m_header.entry.prev_entry = PowerPC::HostRead_U32(guard, address + 0x04);
-  m_header.entry.section_count = PowerPC::HostRead_U32(guard, address + 0x08);
-  m_header.entry.section_table_offset = PowerPC::HostRead_U32(guard, address + 0xC);
-  m_header.entry.name_offset = PowerPC::HostRead_U32(guard, address + 0x10);
-  m_header.entry.name_size = PowerPC::HostRead_U32(guard, address + 0x14);
-  m_header.entry.version = PowerPC::HostRead_U32(guard, address + 0x18);
-  m_header.entry.bss_size = PowerPC::HostRead_U32(guard, address + 0x1C);
-  m_header.section_info.prolog_section_index = PowerPC::HostRead_U8(guard, address + 0x20);
-  m_header.section_info.epilog_section_index = PowerPC::HostRead_U8(guard, address + 0x21);
-  m_header.section_info.unresolved_section_index = PowerPC::HostRead_U8(guard, address + 0x22);
-  m_header.section_info.bss_section_index = PowerPC::HostRead_U8(guard, address + 0x23);
-  m_header.section_info.prolog_offset = PowerPC::HostRead_U32(guard, address + 0x24);
-  m_header.section_info.epilog_offset = PowerPC::HostRead_U32(guard, address + 0x28);
-  m_header.section_info.unresolved_offset = PowerPC::HostRead_U32(guard, address + 0x2C);
-  m_header.relocation_tables.internals_offset = PowerPC::HostRead_U32(guard, address + 0x30);
-  m_header.relocation_tables.internals_size = PowerPC::HostRead_U32(guard, address + 0x34);
-  m_header.relocation_tables.externals_offset = PowerPC::HostRead_U32(guard, address + 0x38);
-  m_header.relocation_tables.externals_size = PowerPC::HostRead_U32(guard, address + 0x3C);
-  m_header.symbol_tables.exports_offset = PowerPC::HostRead_U32(guard, address + 0x40);
-  m_header.symbol_tables.exports_size = PowerPC::HostRead_U32(guard, address + 0x44);
-  m_header.symbol_tables.exports_name_table = PowerPC::HostRead_U32(guard, address + 0x48);
-  m_header.symbol_tables.imports_offset = PowerPC::HostRead_U32(guard, address + 0x4C);
-  m_header.symbol_tables.imports_size = PowerPC::HostRead_U32(guard, address + 0x50);
-  m_header.symbol_tables.imports_name_table = PowerPC::HostRead_U32(guard, address + 0x54);
+  m_header.entry.next_entry = PowerPC::MMU::HostRead_U32(guard, address);
+  m_header.entry.prev_entry = PowerPC::MMU::HostRead_U32(guard, address + 0x04);
+  m_header.entry.section_count = PowerPC::MMU::HostRead_U32(guard, address + 0x08);
+  m_header.entry.section_table_offset = PowerPC::MMU::HostRead_U32(guard, address + 0xC);
+  m_header.entry.name_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x10);
+  m_header.entry.name_size = PowerPC::MMU::HostRead_U32(guard, address + 0x14);
+  m_header.entry.version = PowerPC::MMU::HostRead_U32(guard, address + 0x18);
+  m_header.entry.bss_size = PowerPC::MMU::HostRead_U32(guard, address + 0x1C);
+  m_header.section_info.prolog_section_index = PowerPC::MMU::HostRead_U8(guard, address + 0x20);
+  m_header.section_info.epilog_section_index = PowerPC::MMU::HostRead_U8(guard, address + 0x21);
+  m_header.section_info.unresolved_section_index = PowerPC::MMU::HostRead_U8(guard, address + 0x22);
+  m_header.section_info.bss_section_index = PowerPC::MMU::HostRead_U8(guard, address + 0x23);
+  m_header.section_info.prolog_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x24);
+  m_header.section_info.epilog_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x28);
+  m_header.section_info.unresolved_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x2C);
+  m_header.relocation_tables.internals_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x30);
+  m_header.relocation_tables.internals_size = PowerPC::MMU::HostRead_U32(guard, address + 0x34);
+  m_header.relocation_tables.externals_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x38);
+  m_header.relocation_tables.externals_size = PowerPC::MMU::HostRead_U32(guard, address + 0x3C);
+  m_header.symbol_tables.exports_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x40);
+  m_header.symbol_tables.exports_size = PowerPC::MMU::HostRead_U32(guard, address + 0x44);
+  m_header.symbol_tables.exports_name_table = PowerPC::MMU::HostRead_U32(guard, address + 0x48);
+  m_header.symbol_tables.imports_offset = PowerPC::MMU::HostRead_U32(guard, address + 0x4C);
+  m_header.symbol_tables.imports_size = PowerPC::MMU::HostRead_U32(guard, address + 0x50);
+  m_header.symbol_tables.imports_name_table = PowerPC::MMU::HostRead_U32(guard, address + 0x54);
 
   // Prevent an invalid name going wild
   if (m_header.entry.name_size < 0x100)
-    m_name = PowerPC::HostGetString(guard, m_header.entry.name_offset, m_header.entry.name_size);
+  {
+    m_name =
+        PowerPC::MMU::HostGetString(guard, m_header.entry.name_offset, m_header.entry.name_size);
+  }
   else
-    m_name = PowerPC::HostGetString(guard, m_header.entry.name_offset, 0x100);
+  {
+    m_name = PowerPC::MMU::HostGetString(guard, m_header.entry.name_offset, 0x100);
+  }
 }
 
 u32 RSOHeaderView::GetNextEntry() const
@@ -176,8 +181,8 @@ void RSOSectionsView::Load(const Core::CPUThreadGuard& guard, u32 address, std::
   for (std::size_t i = 0; i < count; ++i)
   {
     RSOSection section;
-    section.offset = PowerPC::HostRead_U32(guard, address);
-    section.size = PowerPC::HostRead_U32(guard, address + 4);
+    section.offset = PowerPC::MMU::HostRead_U32(guard, address);
+    section.size = PowerPC::MMU::HostRead_U32(guard, address + 4);
     m_sections.emplace_back(std::move(section));
     address += sizeof(RSOSection);
   }
@@ -204,9 +209,9 @@ void RSOImportsView::Load(const Core::CPUThreadGuard& guard, u32 address, std::s
   for (std::size_t i = 0; i < count; ++i)
   {
     RSOImport rso_import;
-    rso_import.name_offset = PowerPC::HostRead_U32(guard, address);
-    rso_import.code_offset = PowerPC::HostRead_U32(guard, address + 4);
-    rso_import.entry_offset = PowerPC::HostRead_U32(guard, address + 8);
+    rso_import.name_offset = PowerPC::MMU::HostRead_U32(guard, address);
+    rso_import.code_offset = PowerPC::MMU::HostRead_U32(guard, address + 4);
+    rso_import.entry_offset = PowerPC::MMU::HostRead_U32(guard, address + 8);
     m_imports.emplace_back(std::move(rso_import));
     address += sizeof(RSOImport);
   }
@@ -233,10 +238,10 @@ void RSOExportsView::Load(const Core::CPUThreadGuard& guard, u32 address, std::s
   for (std::size_t i = 0; i < count; ++i)
   {
     RSOExport rso_export;
-    rso_export.name_offset = PowerPC::HostRead_U32(guard, address);
-    rso_export.code_offset = PowerPC::HostRead_U32(guard, address + 4);
-    rso_export.section_index = PowerPC::HostRead_U32(guard, address + 8);
-    rso_export.hash = PowerPC::HostRead_U32(guard, address + 12);
+    rso_export.name_offset = PowerPC::MMU::HostRead_U32(guard, address);
+    rso_export.code_offset = PowerPC::MMU::HostRead_U32(guard, address + 4);
+    rso_export.section_index = PowerPC::MMU::HostRead_U32(guard, address + 8);
+    rso_export.hash = PowerPC::MMU::HostRead_U32(guard, address + 12);
     m_exports.emplace_back(std::move(rso_export));
     address += sizeof(RSOExport);
   }
@@ -263,9 +268,9 @@ void RSOInternalsView::Load(const Core::CPUThreadGuard& guard, u32 address, std:
   for (std::size_t i = 0; i < count; ++i)
   {
     RSOInternalsEntry entry;
-    entry.r_offset = PowerPC::HostRead_U32(guard, address);
-    entry.r_info = PowerPC::HostRead_U32(guard, address + 4);
-    entry.r_addend = PowerPC::HostRead_U32(guard, address + 8);
+    entry.r_offset = PowerPC::MMU::HostRead_U32(guard, address);
+    entry.r_info = PowerPC::MMU::HostRead_U32(guard, address + 4);
+    entry.r_addend = PowerPC::MMU::HostRead_U32(guard, address + 8);
     m_entries.emplace_back(std::move(entry));
     address += sizeof(RSOInternalsEntry);
   }
@@ -292,9 +297,9 @@ void RSOExternalsView::Load(const Core::CPUThreadGuard& guard, u32 address, std:
   for (std::size_t i = 0; i < count; ++i)
   {
     RSOExternalsEntry entry;
-    entry.r_offset = PowerPC::HostRead_U32(guard, address);
-    entry.r_info = PowerPC::HostRead_U32(guard, address + 4);
-    entry.r_addend = PowerPC::HostRead_U32(guard, address + 8);
+    entry.r_offset = PowerPC::MMU::HostRead_U32(guard, address);
+    entry.r_info = PowerPC::MMU::HostRead_U32(guard, address + 4);
+    entry.r_addend = PowerPC::MMU::HostRead_U32(guard, address + 8);
     m_entries.emplace_back(std::move(entry));
     address += sizeof(RSOExternalsEntry);
   }
@@ -443,7 +448,8 @@ const RSOImport& RSOView::GetImport(std::size_t index) const
 std::string RSOView::GetImportName(const Core::CPUThreadGuard& guard,
                                    const RSOImport& rso_import) const
 {
-  return PowerPC::HostGetString(guard, m_header.GetImportsNameTable() + rso_import.name_offset);
+  return PowerPC::MMU::HostGetString(guard,
+                                     m_header.GetImportsNameTable() + rso_import.name_offset);
 }
 
 const std::vector<RSOImport>& RSOView::GetImports() const
@@ -464,7 +470,8 @@ const RSOExport& RSOView::GetExport(std::size_t index) const
 std::string RSOView::GetExportName(const Core::CPUThreadGuard& guard,
                                    const RSOExport& rso_export) const
 {
-  return PowerPC::HostGetString(guard, m_header.GetExportsNameTable() + rso_export.name_offset);
+  return PowerPC::MMU::HostGetString(guard,
+                                     m_header.GetExportsNameTable() + rso_export.name_offset);
 }
 
 u32 RSOView::GetExportAddress(const RSOExport& rso_export) const
diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp
index 61905d98ae..baf7b87760 100644
--- a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp
+++ b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp
@@ -654,8 +654,10 @@ void FifoPlayer::LoadMemory()
   ppc_state.spr[SPR_DBAT0L] = 0x00000002;
   ppc_state.spr[SPR_DBAT1U] = 0xc0001fff;
   ppc_state.spr[SPR_DBAT1L] = 0x0000002a;
-  PowerPC::DBATUpdated();
-  PowerPC::IBATUpdated();
+
+  auto& mmu = system.GetMMU();
+  mmu.DBATUpdated();
+  mmu.IBATUpdated();
 
   SetupFifo();
   LoadRegisters();
@@ -713,12 +715,12 @@ void FifoPlayer::LoadTextureMemory()
 
 void FifoPlayer::WriteCP(u32 address, u16 value)
 {
-  PowerPC::Write_U16(value, 0xCC000000 | address);
+  Core::System::GetInstance().GetMMU().Write_U16(value, 0xCC000000 | address);
 }
 
 void FifoPlayer::WritePI(u32 address, u32 value)
 {
-  PowerPC::Write_U32(value, 0xCC003000 | address);
+  Core::System::GetInstance().GetMMU().Write_U32(value, 0xCC003000 | address);
 }
 
 void FifoPlayer::FlushWGP()
@@ -823,13 +825,13 @@ bool FifoPlayer::ShouldLoadXF(u8 reg)
 bool FifoPlayer::IsIdleSet()
 {
   CommandProcessor::UCPStatusReg status =
-      PowerPC::Read_U16(0xCC000000 | CommandProcessor::STATUS_REGISTER);
+      Core::System::GetInstance().GetMMU().Read_U16(0xCC000000 | CommandProcessor::STATUS_REGISTER);
   return status.CommandIdle;
 }
 
 bool FifoPlayer::IsHighWatermarkSet()
 {
   CommandProcessor::UCPStatusReg status =
-      PowerPC::Read_U16(0xCC000000 | CommandProcessor::STATUS_REGISTER);
+      Core::System::GetInstance().GetMMU().Read_U16(0xCC000000 | CommandProcessor::STATUS_REGISTER);
   return status.OverflowHiWatermark;
 }
diff --git a/Source/Core/Core/GeckoCode.cpp b/Source/Core/Core/GeckoCode.cpp
index f77f09203f..c28178ae8e 100644
--- a/Source/Core/Core/GeckoCode.cpp
+++ b/Source/Core/Core/GeckoCode.cpp
@@ -142,17 +142,17 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
 
   // Install code handler
   for (u32 i = 0; i < data.size(); ++i)
-    PowerPC::HostWrite_U8(guard, data[i], INSTALLER_BASE_ADDRESS + i);
+    PowerPC::MMU::HostWrite_U8(guard, data[i], INSTALLER_BASE_ADDRESS + i);
 
   // Patch the code handler to the current system type (Gamecube/Wii)
   for (unsigned int h = 0; h < data.length(); h += 4)
   {
     // Patch MMIO address
-    if (PowerPC::HostRead_U32(guard, INSTALLER_BASE_ADDRESS + h) ==
+    if (PowerPC::MMU::HostRead_U32(guard, INSTALLER_BASE_ADDRESS + h) ==
         (0x3f000000u | ((mmio_addr ^ 1) << 8)))
     {
       NOTICE_LOG_FMT(ACTIONREPLAY, "Patching MMIO access at {:08x}", INSTALLER_BASE_ADDRESS + h);
-      PowerPC::HostWrite_U32(guard, 0x3f000000u | mmio_addr << 8, INSTALLER_BASE_ADDRESS + h);
+      PowerPC::MMU::HostWrite_U32(guard, 0x3f000000u | mmio_addr << 8, INSTALLER_BASE_ADDRESS + h);
     }
   }
 
@@ -162,11 +162,11 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
 
   // Write a magic value to 'gameid' (codehandleronly does not actually read this).
   // This value will be read back and modified over time by HLE_Misc::GeckoCodeHandlerICacheFlush.
-  PowerPC::HostWrite_U32(guard, MAGIC_GAMEID, INSTALLER_BASE_ADDRESS);
+  PowerPC::MMU::HostWrite_U32(guard, MAGIC_GAMEID, INSTALLER_BASE_ADDRESS);
 
   // Create GCT in memory
-  PowerPC::HostWrite_U32(guard, 0x00d0c0de, codelist_base_address);
-  PowerPC::HostWrite_U32(guard, 0x00d0c0de, codelist_base_address + 4);
+  PowerPC::MMU::HostWrite_U32(guard, 0x00d0c0de, codelist_base_address);
+  PowerPC::MMU::HostWrite_U32(guard, 0x00d0c0de, codelist_base_address + 4);
 
   // Each code is 8 bytes (2 words) wide. There is a starter code and an end code.
   const u32 start_address = codelist_base_address + CODE_SIZE;
@@ -189,8 +189,8 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
 
     for (const GeckoCode::Code& code : active_code.codes)
     {
-      PowerPC::HostWrite_U32(guard, code.address, next_address);
-      PowerPC::HostWrite_U32(guard, code.data, next_address + 4);
+      PowerPC::MMU::HostWrite_U32(guard, code.address, next_address);
+      PowerPC::MMU::HostWrite_U32(guard, code.data, next_address + 4);
       next_address += CODE_SIZE;
     }
   }
@@ -199,12 +199,12 @@ static Installation InstallCodeHandlerLocked(const Core::CPUThreadGuard& guard)
                end_address - start_address);
 
   // Stop code. Tells the handler that this is the end of the list.
-  PowerPC::HostWrite_U32(guard, 0xF0000000, next_address);
-  PowerPC::HostWrite_U32(guard, 0x00000000, next_address + 4);
-  PowerPC::HostWrite_U32(guard, 0, HLE_TRAMPOLINE_ADDRESS);
+  PowerPC::MMU::HostWrite_U32(guard, 0xF0000000, next_address);
+  PowerPC::MMU::HostWrite_U32(guard, 0x00000000, next_address + 4);
+  PowerPC::MMU::HostWrite_U32(guard, 0, HLE_TRAMPOLINE_ADDRESS);
 
   // Turn on codes
-  PowerPC::HostWrite_U8(guard, 1, INSTALLER_BASE_ADDRESS + 7);
+  PowerPC::MMU::HostWrite_U8(guard, 1, INSTALLER_BASE_ADDRESS + 7);
 
   auto& system = Core::System::GetInstance();
   auto& ppc_state = system.GetPPCState();
@@ -274,17 +274,18 @@ void RunCodeHandler(const Core::CPUThreadGuard& guard)
   ppc_state.gpr[1] -= 8;                          // Fake stack frame for codehandler
   ppc_state.gpr[1] &= 0xFFFFFFF0;                 // Align stack to 16bytes
   u32 SP = ppc_state.gpr[1];                      // Stack Pointer
-  PowerPC::HostWrite_U32(guard, SP + 8, SP);
+  PowerPC::MMU::HostWrite_U32(guard, SP + 8, SP);
   // SP + 4 is reserved for the codehandler to save LR to the stack.
-  PowerPC::HostWrite_U32(guard, SFP, SP + 8);  // Real stack frame
-  PowerPC::HostWrite_U32(guard, ppc_state.pc, SP + 12);
-  PowerPC::HostWrite_U32(guard, LR(ppc_state), SP + 16);
-  PowerPC::HostWrite_U32(guard, ppc_state.cr.Get(), SP + 20);
+  PowerPC::MMU::HostWrite_U32(guard, SFP, SP + 8);  // Real stack frame
+  PowerPC::MMU::HostWrite_U32(guard, ppc_state.pc, SP + 12);
+  PowerPC::MMU::HostWrite_U32(guard, LR(ppc_state), SP + 16);
+  PowerPC::MMU::HostWrite_U32(guard, ppc_state.cr.Get(), SP + 20);
   // Registers FPR0->13 are volatile
   for (int i = 0; i < 14; ++i)
   {
-    PowerPC::HostWrite_U64(guard, ppc_state.ps[i].PS0AsU64(), SP + 24 + 2 * i * sizeof(u64));
-    PowerPC::HostWrite_U64(guard, ppc_state.ps[i].PS1AsU64(), SP + 24 + (2 * i + 1) * sizeof(u64));
+    PowerPC::MMU::HostWrite_U64(guard, ppc_state.ps[i].PS0AsU64(), SP + 24 + 2 * i * sizeof(u64));
+    PowerPC::MMU::HostWrite_U64(guard, ppc_state.ps[i].PS1AsU64(),
+                                SP + 24 + (2 * i + 1) * sizeof(u64));
   }
   DEBUG_LOG_FMT(ACTIONREPLAY,
                 "GeckoCodes: Initiating phantom branch-and-link. "
diff --git a/Source/Core/Core/HLE/HLE_Misc.cpp b/Source/Core/Core/HLE/HLE_Misc.cpp
index bcc1bec512..e2adaffebb 100644
--- a/Source/Core/Core/HLE/HLE_Misc.cpp
+++ b/Source/Core/Core/HLE/HLE_Misc.cpp
@@ -42,7 +42,7 @@ void GeckoCodeHandlerICacheFlush(const Core::CPUThreadGuard& guard)
   // been read into memory, or such, so we do the first 5 frames.  More
   // robust alternative would be to actually detect memory writes, but that
   // would be even uglier.)
-  u32 gch_gameid = PowerPC::HostRead_U32(guard, Gecko::INSTALLER_BASE_ADDRESS);
+  u32 gch_gameid = PowerPC::MMU::HostRead_U32(guard, Gecko::INSTALLER_BASE_ADDRESS);
   if (gch_gameid - Gecko::MAGIC_GAMEID == 5)
   {
     return;
@@ -51,7 +51,7 @@ void GeckoCodeHandlerICacheFlush(const Core::CPUThreadGuard& guard)
   {
     gch_gameid = Gecko::MAGIC_GAMEID;
   }
-  PowerPC::HostWrite_U32(guard, gch_gameid + 1, Gecko::INSTALLER_BASE_ADDRESS);
+  PowerPC::MMU::HostWrite_U32(guard, gch_gameid + 1, Gecko::INSTALLER_BASE_ADDRESS);
 
   ppc_state.iCache.Reset();
 }
@@ -67,14 +67,14 @@ void GeckoReturnTrampoline(const Core::CPUThreadGuard& guard)
 
   // Stack frame is built in GeckoCode.cpp, Gecko::RunCodeHandler.
   u32 SP = ppc_state.gpr[1];
-  ppc_state.gpr[1] = PowerPC::HostRead_U32(guard, SP + 8);
-  ppc_state.npc = PowerPC::HostRead_U32(guard, SP + 12);
-  LR(ppc_state) = PowerPC::HostRead_U32(guard, SP + 16);
-  ppc_state.cr.Set(PowerPC::HostRead_U32(guard, SP + 20));
+  ppc_state.gpr[1] = PowerPC::MMU::HostRead_U32(guard, SP + 8);
+  ppc_state.npc = PowerPC::MMU::HostRead_U32(guard, SP + 12);
+  LR(ppc_state) = PowerPC::MMU::HostRead_U32(guard, SP + 16);
+  ppc_state.cr.Set(PowerPC::MMU::HostRead_U32(guard, SP + 20));
   for (int i = 0; i < 14; ++i)
   {
-    ppc_state.ps[i].SetBoth(PowerPC::HostRead_U64(guard, SP + 24 + 2 * i * sizeof(u64)),
-                            PowerPC::HostRead_U64(guard, SP + 24 + (2 * i + 1) * sizeof(u64)));
+    ppc_state.ps[i].SetBoth(PowerPC::MMU::HostRead_U64(guard, SP + 24 + 2 * i * sizeof(u64)),
+                            PowerPC::MMU::HostRead_U64(guard, SP + 24 + (2 * i + 1) * sizeof(u64)));
   }
 }
 }  // namespace HLE_Misc
diff --git a/Source/Core/Core/HLE/HLE_OS.cpp b/Source/Core/Core/HLE/HLE_OS.cpp
index 7630b42430..71a27a4ec8 100644
--- a/Source/Core/Core/HLE/HLE_OS.cpp
+++ b/Source/Core/Core/HLE/HLE_OS.cpp
@@ -10,6 +10,7 @@
 #include "Common/Logging/Log.h"
 #include "Common/MsgHandler.h"
 #include "Common/StringUtil.h"
+#include "Core/Core.h"
 #include "Core/HLE/HLE_VarArgs.h"
 #include "Core/PowerPC/MMU.h"
 #include "Core/PowerPC/PowerPC.h"
@@ -56,11 +57,11 @@ void HLE_GeneralDebugPrint(const Core::CPUThreadGuard& guard, ParameterType para
   std::string report_message;
 
   // Is gpr3 pointing to a pointer (including nullptr) rather than an ASCII string
-  if (PowerPC::HostIsRAMAddress(guard, ppc_state.gpr[3]) &&
-      (PowerPC::HostIsRAMAddress(guard, PowerPC::HostRead_U32(guard, ppc_state.gpr[3])) ||
-       PowerPC::HostRead_U32(guard, ppc_state.gpr[3]) == 0))
+  if (PowerPC::MMU::HostIsRAMAddress(guard, ppc_state.gpr[3]) &&
+      (PowerPC::MMU::HostIsRAMAddress(guard, PowerPC::MMU::HostRead_U32(guard, ppc_state.gpr[3])) ||
+       PowerPC::MMU::HostRead_U32(guard, ppc_state.gpr[3]) == 0))
   {
-    if (PowerPC::HostIsRAMAddress(guard, ppc_state.gpr[4]))
+    if (PowerPC::MMU::HostIsRAMAddress(guard, ppc_state.gpr[4]))
     {
       // ___blank(void* this, const char* fmt, ...);
       report_message = GetStringVA(system, guard, 4, parameter_type);
@@ -73,7 +74,7 @@ void HLE_GeneralDebugPrint(const Core::CPUThreadGuard& guard, ParameterType para
   }
   else
   {
-    if (PowerPC::HostIsRAMAddress(guard, ppc_state.gpr[3]))
+    if (PowerPC::MMU::HostIsRAMAddress(guard, ppc_state.gpr[3]))
     {
       // ___blank(const char* fmt, ...);
       report_message = GetStringVA(system, guard, 3, parameter_type);
@@ -106,13 +107,13 @@ void HLE_GeneralDebugVPrint(const Core::CPUThreadGuard& guard)
 // __write_console(int fd, const void* buffer, const u32* size)
 void HLE_write_console(const Core::CPUThreadGuard& guard)
 {
-  auto& system = Core::System::GetInstance();
+  auto& system = guard.GetSystem();
   auto& ppc_state = system.GetPPCState();
 
   std::string report_message = GetStringVA(system, guard, 4);
-  if (PowerPC::HostIsRAMAddress(guard, ppc_state.gpr[5]))
+  if (PowerPC::MMU::HostIsRAMAddress(guard, ppc_state.gpr[5]))
   {
-    const u32 size = PowerPC::Read_U32(ppc_state.gpr[5]);
+    const u32 size = system.GetMMU().Read_U32(ppc_state.gpr[5]);
     if (size > report_message.size())
       WARN_LOG_FMT(OSREPORT_HLE, "__write_console uses an invalid size of {:#010x}", size);
     else if (size == 0)
@@ -169,16 +170,16 @@ void HLE_LogFPrint(const Core::CPUThreadGuard& guard, ParameterType parameter_ty
   // The structure FILE is implementation defined.
   // Both libogc and Dolphin SDK seem to store the fd at the same address.
   int fd = -1;
-  if (PowerPC::HostIsRAMAddress(guard, ppc_state.gpr[3]) &&
-      PowerPC::HostIsRAMAddress(guard, ppc_state.gpr[3] + 0xF))
+  if (PowerPC::MMU::HostIsRAMAddress(guard, ppc_state.gpr[3]) &&
+      PowerPC::MMU::HostIsRAMAddress(guard, ppc_state.gpr[3] + 0xF))
   {
     // The fd is stored as a short at FILE+0xE.
-    fd = static_cast<short>(PowerPC::HostRead_U16(guard, ppc_state.gpr[3] + 0xE));
+    fd = static_cast<short>(PowerPC::MMU::HostRead_U16(guard, ppc_state.gpr[3] + 0xE));
   }
   if (fd != 1 && fd != 2)
   {
     // On RVL SDK it seems stored at FILE+0x2.
-    fd = static_cast<short>(PowerPC::HostRead_U16(guard, ppc_state.gpr[3] + 0x2));
+    fd = static_cast<short>(PowerPC::MMU::HostRead_U16(guard, ppc_state.gpr[3] + 0x2));
   }
   if (fd != 1 && fd != 2)
     return;
@@ -210,7 +211,7 @@ std::string GetStringVA(Core::System& system, const Core::CPUThreadGuard& guard,
 
   std::string ArgumentBuffer;
   std::string result;
-  std::string string = PowerPC::HostGetString(guard, ppc_state.gpr[str_reg]);
+  std::string string = PowerPC::MMU::HostGetString(guard, ppc_state.gpr[str_reg]);
   auto ap =
       parameter_type == ParameterType::VariableArgumentList ?
           std::make_unique<HLE::SystemVABI::VAListStruct>(system, guard,
@@ -242,8 +243,9 @@ std::string GetStringVA(Core::System& system, const Core::CPUThreadGuard& guard,
       switch (string[i])
       {
       case 's':
-        result += StringFromFormat(ArgumentBuffer.c_str(),
-                                   PowerPC::HostGetString(guard, ap->GetArgT<u32>(guard)).c_str());
+        result +=
+            StringFromFormat(ArgumentBuffer.c_str(),
+                             PowerPC::MMU::HostGetString(guard, ap->GetArgT<u32>(guard)).c_str());
         break;
 
       case 'a':
diff --git a/Source/Core/Core/HLE/HLE_VarArgs.cpp b/Source/Core/Core/HLE/HLE_VarArgs.cpp
index 0e7e65561b..4c792e973e 100644
--- a/Source/Core/Core/HLE/HLE_VarArgs.cpp
+++ b/Source/Core/Core/HLE/HLE_VarArgs.cpp
@@ -20,10 +20,10 @@ double HLE::SystemVABI::VAList::GetFPR(const Core::CPUThreadGuard&, u32 fpr) con
 
 HLE::SystemVABI::VAListStruct::VAListStruct(Core::System& system, const Core::CPUThreadGuard& guard,
                                             u32 address)
-    : VAList(system, 0), m_va_list{PowerPC::HostRead_U8(guard, address),
-                                   PowerPC::HostRead_U8(guard, address + 1),
-                                   PowerPC::HostRead_U32(guard, address + 4),
-                                   PowerPC::HostRead_U32(guard, address + 8)},
+    : VAList(system, 0), m_va_list{PowerPC::MMU::HostRead_U8(guard, address),
+                                   PowerPC::MMU::HostRead_U8(guard, address + 1),
+                                   PowerPC::MMU::HostRead_U32(guard, address + 4),
+                                   PowerPC::MMU::HostRead_U32(guard, address + 8)},
       m_address(address), m_has_fpr_area(system.GetPPCState().cr.GetBit(6) == 1)
 {
   m_stack = m_va_list.overflow_arg_area;
@@ -49,7 +49,7 @@ u32 HLE::SystemVABI::VAListStruct::GetGPR(const Core::CPUThreadGuard& guard, u32
     return 0;
   }
   const u32 gpr_address = Common::AlignUp(GetGPRArea() + 4 * (gpr - 3), 4);
-  return PowerPC::HostRead_U32(guard, gpr_address);
+  return PowerPC::MMU::HostRead_U32(guard, gpr_address);
 }
 
 double HLE::SystemVABI::VAListStruct::GetFPR(const Core::CPUThreadGuard& guard, u32 fpr) const
@@ -60,5 +60,5 @@ double HLE::SystemVABI::VAListStruct::GetFPR(const Core::CPUThreadGuard& guard,
     return 0.0;
   }
   const u32 fpr_address = Common::AlignUp(GetFPRArea() + 8 * (fpr - 1), 8);
-  return PowerPC::HostRead_F64(guard, fpr_address);
+  return PowerPC::MMU::HostRead_F64(guard, fpr_address);
 }
diff --git a/Source/Core/Core/HLE/HLE_VarArgs.h b/Source/Core/Core/HLE/HLE_VarArgs.h
index 6ed4919a69..eecf231425 100644
--- a/Source/Core/Core/HLE/HLE_VarArgs.h
+++ b/Source/Core/Core/HLE/HLE_VarArgs.h
@@ -55,7 +55,7 @@ public:
 
     for (size_t i = 0; i < sizeof(T); i += 1, addr += 1)
     {
-      reinterpret_cast<u8*>(&obj)[i] = PowerPC::HostRead_U8(guard, addr);
+      reinterpret_cast<u8*>(&obj)[i] = PowerPC::MMU::HostRead_U8(guard, addr);
     }
 
     return obj;
@@ -76,7 +76,7 @@ public:
     else
     {
       m_stack = Common::AlignUp(m_stack, 4);
-      value = PowerPC::HostRead_U32(guard, m_stack);
+      value = PowerPC::MMU::HostRead_U32(guard, m_stack);
       m_stack += 4;
     }
 
@@ -99,7 +99,7 @@ public:
     else
     {
       m_stack = Common::AlignUp(m_stack, 8);
-      value = PowerPC::HostRead_U64(guard, m_stack);
+      value = PowerPC::MMU::HostRead_U64(guard, m_stack);
       m_stack += 8;
     }
 
@@ -120,7 +120,7 @@ public:
     else
     {
       m_stack = Common::AlignUp(m_stack, 8);
-      value = PowerPC::HostRead_F64(guard, m_stack);
+      value = PowerPC::MMU::HostRead_F64(guard, m_stack);
       m_stack += 8;
     }
 
diff --git a/Source/Core/Core/HW/AddressSpace.cpp b/Source/Core/Core/HW/AddressSpace.cpp
index 0bacf52c2d..5556135146 100644
--- a/Source/Core/Core/HW/AddressSpace.cpp
+++ b/Source/Core/Core/HW/AddressSpace.cpp
@@ -84,43 +84,43 @@ struct EffectiveAddressSpaceAccessors : Accessors
 {
   bool IsValidAddress(const Core::CPUThreadGuard& guard, u32 address) const override
   {
-    return PowerPC::HostIsRAMAddress(guard, address);
+    return PowerPC::MMU::HostIsRAMAddress(guard, address);
   }
   u8 ReadU8(const Core::CPUThreadGuard& guard, u32 address) const override
   {
-    return PowerPC::HostRead_U8(guard, address);
+    return PowerPC::MMU::HostRead_U8(guard, address);
   }
   void WriteU8(const Core::CPUThreadGuard& guard, u32 address, u8 value) override
   {
-    PowerPC::HostWrite_U8(guard, value, address);
+    PowerPC::MMU::HostWrite_U8(guard, value, address);
   }
   u16 ReadU16(const Core::CPUThreadGuard& guard, u32 address) const override
   {
-    return PowerPC::HostRead_U16(guard, address);
+    return PowerPC::MMU::HostRead_U16(guard, address);
   }
   void WriteU16(const Core::CPUThreadGuard& guard, u32 address, u16 value) override
   {
-    PowerPC::HostWrite_U16(guard, value, address);
+    PowerPC::MMU::HostWrite_U16(guard, value, address);
   }
   u32 ReadU32(const Core::CPUThreadGuard& guard, u32 address) const override
   {
-    return PowerPC::HostRead_U32(guard, address);
+    return PowerPC::MMU::HostRead_U32(guard, address);
   }
   void WriteU32(const Core::CPUThreadGuard& guard, u32 address, u32 value) override
   {
-    PowerPC::HostWrite_U32(guard, value, address);
+    PowerPC::MMU::HostWrite_U32(guard, value, address);
   }
   u64 ReadU64(const Core::CPUThreadGuard& guard, u32 address) const override
   {
-    return PowerPC::HostRead_U64(guard, address);
+    return PowerPC::MMU::HostRead_U64(guard, address);
   }
   void WriteU64(const Core::CPUThreadGuard& guard, u32 address, u64 value) override
   {
-    PowerPC::HostWrite_U64(guard, value, address);
+    PowerPC::MMU::HostWrite_U64(guard, value, address);
   }
   float ReadF32(const Core::CPUThreadGuard& guard, u32 address) const override
   {
-    return PowerPC::HostRead_F32(guard, address);
+    return PowerPC::MMU::HostRead_F32(guard, address);
   };
 
   bool Matches(const Core::CPUThreadGuard& guard, u32 haystack_start, const u8* needle_start,
@@ -128,16 +128,17 @@ struct EffectiveAddressSpaceAccessors : Accessors
   {
     auto& system = guard.GetSystem();
     auto& memory = system.GetMemory();
+    auto& mmu = system.GetMMU();
 
     u32 page_base = haystack_start & 0xfffff000;
     u32 offset = haystack_start & 0x0000fff;
     do
     {
-      if (!PowerPC::HostIsRAMAddress(guard, page_base))
+      if (!PowerPC::MMU::HostIsRAMAddress(guard, page_base))
       {
         return false;
       }
-      auto page_physical_address = PowerPC::GetTranslatedAddress(page_base);
+      auto page_physical_address = mmu.GetTranslatedAddress(page_base);
       if (!page_physical_address.has_value())
       {
         return false;
@@ -184,7 +185,7 @@ struct EffectiveAddressSpaceAccessors : Accessors
     const u32 haystack_offset_change = forward ? 1 : -1;
     do
     {
-      if (PowerPC::HostIsRAMAddress(guard, haystack_address))
+      if (PowerPC::MMU::HostIsRAMAddress(guard, haystack_address))
       {
         do
         {
diff --git a/Source/Core/Core/MemoryWatcher.cpp b/Source/Core/Core/MemoryWatcher.cpp
index f2456f6d57..38cdebe1b4 100644
--- a/Source/Core/Core/MemoryWatcher.cpp
+++ b/Source/Core/Core/MemoryWatcher.cpp
@@ -72,8 +72,8 @@ u32 MemoryWatcher::ChasePointer(const Core::CPUThreadGuard& guard, const std::st
   u32 value = 0;
   for (u32 offset : m_addresses[line])
   {
-    value = PowerPC::HostRead_U32(guard, value + offset);
-    if (!PowerPC::HostIsRAMAddress(guard, value))
+    value = PowerPC::MMU::HostRead_U32(guard, value + offset);
+    if (!PowerPC::MMU::HostIsRAMAddress(guard, value))
       break;
   }
   return value;
diff --git a/Source/Core/Core/PatchEngine.cpp b/Source/Core/Core/PatchEngine.cpp
index 4f4f9b9090..b180991522 100644
--- a/Source/Core/Core/PatchEngine.cpp
+++ b/Source/Core/Core/PatchEngine.cpp
@@ -245,17 +245,22 @@ static void ApplyPatches(const Core::CPUThreadGuard& guard, const std::vector<Pa
         switch (entry.type)
         {
         case PatchType::Patch8Bit:
-          if (!entry.conditional || PowerPC::HostRead_U8(guard, addr) == static_cast<u8>(comparand))
-            PowerPC::HostWrite_U8(guard, static_cast<u8>(value), addr);
+          if (!entry.conditional ||
+              PowerPC::MMU::HostRead_U8(guard, addr) == static_cast<u8>(comparand))
+          {
+            PowerPC::MMU::HostWrite_U8(guard, static_cast<u8>(value), addr);
+          }
           break;
         case PatchType::Patch16Bit:
           if (!entry.conditional ||
-              PowerPC::HostRead_U16(guard, addr) == static_cast<u16>(comparand))
-            PowerPC::HostWrite_U16(guard, static_cast<u16>(value), addr);
+              PowerPC::MMU::HostRead_U16(guard, addr) == static_cast<u16>(comparand))
+          {
+            PowerPC::MMU::HostWrite_U16(guard, static_cast<u16>(value), addr);
+          }
           break;
         case PatchType::Patch32Bit:
-          if (!entry.conditional || PowerPC::HostRead_U32(guard, addr) == comparand)
-            PowerPC::HostWrite_U32(guard, value, addr);
+          if (!entry.conditional || PowerPC::MMU::HostRead_U32(guard, addr) == comparand)
+            PowerPC::MMU::HostWrite_U32(guard, value, addr);
           break;
         default:
           // unknown patchtype
@@ -288,19 +293,19 @@ static bool IsStackValid(const Core::CPUThreadGuard& guard)
 
   // Check the stack pointer
   u32 SP = ppc_state.gpr[1];
-  if (!PowerPC::HostIsRAMAddress(guard, SP))
+  if (!PowerPC::MMU::HostIsRAMAddress(guard, SP))
     return false;
 
   // Read the frame pointer from the stack (find 2nd frame from top), assert that it makes sense
-  u32 next_SP = PowerPC::HostRead_U32(guard, SP);
-  if (next_SP <= SP || !PowerPC::HostIsRAMAddress(guard, next_SP) ||
-      !PowerPC::HostIsRAMAddress(guard, next_SP + 4))
+  u32 next_SP = PowerPC::MMU::HostRead_U32(guard, SP);
+  if (next_SP <= SP || !PowerPC::MMU::HostIsRAMAddress(guard, next_SP) ||
+      !PowerPC::MMU::HostIsRAMAddress(guard, next_SP + 4))
     return false;
 
   // Check the link register makes sense (that it points to a valid IBAT address)
-  const u32 address = PowerPC::HostRead_U32(guard, next_SP + 4);
-  return PowerPC::HostIsInstructionRAMAddress(guard, address) &&
-         0 != PowerPC::HostRead_Instruction(guard, address);
+  const u32 address = PowerPC::MMU::HostRead_U32(guard, next_SP + 4);
+  return PowerPC::MMU::HostIsInstructionRAMAddress(guard, address) &&
+         0 != PowerPC::MMU::HostRead_Instruction(guard, address);
 }
 
 void AddMemoryPatch(std::size_t index)
diff --git a/Source/Core/Core/PowerPC/BreakPoints.cpp b/Source/Core/Core/PowerPC/BreakPoints.cpp
index a3615afb2e..fd30da2b91 100644
--- a/Source/Core/Core/PowerPC/BreakPoints.cpp
+++ b/Source/Core/Core/PowerPC/BreakPoints.cpp
@@ -280,7 +280,7 @@ void MemChecks::Add(TMemCheck memory_check)
     // watchpoint-compatible code.
     if (!had_any)
       Core::System::GetInstance().GetJitInterface().ClearCache();
-    PowerPC::DBATUpdated();
+    Core::System::GetInstance().GetMMU().DBATUpdated();
   });
 }
 
@@ -309,7 +309,7 @@ void MemChecks::Remove(u32 address)
     m_mem_checks.erase(iter);
     if (!HasAny())
       Core::System::GetInstance().GetJitInterface().ClearCache();
-    PowerPC::DBATUpdated();
+    Core::System::GetInstance().GetMMU().DBATUpdated();
   });
 }
 
@@ -318,7 +318,7 @@ void MemChecks::Clear()
   Core::RunAsCPUThread([&] {
     m_mem_checks.clear();
     Core::System::GetInstance().GetJitInterface().ClearCache();
-    PowerPC::DBATUpdated();
+    Core::System::GetInstance().GetMMU().DBATUpdated();
   });
 }
 
diff --git a/Source/Core/Core/PowerPC/Expression.cpp b/Source/Core/Core/PowerPC/Expression.cpp
index 597c77c985..bd089e3a01 100644
--- a/Source/Core/Core/PowerPC/Expression.cpp
+++ b/Source/Core/Core/PowerPC/Expression.cpp
@@ -31,49 +31,49 @@ static void HostWrite(const Core::CPUThreadGuard& guard, T var, u32 address);
 template <>
 u8 HostRead(const Core::CPUThreadGuard& guard, u32 address)
 {
-  return PowerPC::HostRead_U8(guard, address);
+  return PowerPC::MMU::HostRead_U8(guard, address);
 }
 
 template <>
 u16 HostRead(const Core::CPUThreadGuard& guard, u32 address)
 {
-  return PowerPC::HostRead_U16(guard, address);
+  return PowerPC::MMU::HostRead_U16(guard, address);
 }
 
 template <>
 u32 HostRead(const Core::CPUThreadGuard& guard, u32 address)
 {
-  return PowerPC::HostRead_U32(guard, address);
+  return PowerPC::MMU::HostRead_U32(guard, address);
 }
 
 template <>
 u64 HostRead(const Core::CPUThreadGuard& guard, u32 address)
 {
-  return PowerPC::HostRead_U64(guard, address);
+  return PowerPC::MMU::HostRead_U64(guard, address);
 }
 
 template <>
 void HostWrite(const Core::CPUThreadGuard& guard, u8 var, u32 address)
 {
-  PowerPC::HostWrite_U8(guard, var, address);
+  PowerPC::MMU::HostWrite_U8(guard, var, address);
 }
 
 template <>
 void HostWrite(const Core::CPUThreadGuard& guard, u16 var, u32 address)
 {
-  PowerPC::HostWrite_U16(guard, var, address);
+  PowerPC::MMU::HostWrite_U16(guard, var, address);
 }
 
 template <>
 void HostWrite(const Core::CPUThreadGuard& guard, u32 var, u32 address)
 {
-  PowerPC::HostWrite_U32(guard, var, address);
+  PowerPC::MMU::HostWrite_U32(guard, var, address);
 }
 
 template <>
 void HostWrite(const Core::CPUThreadGuard& guard, u64 var, u32 address)
 {
-  PowerPC::HostWrite_U64(guard, var, address);
+  PowerPC::MMU::HostWrite_U64(guard, var, address);
 }
 
 template <typename T, typename U = T>
@@ -146,7 +146,7 @@ static std::optional<std::string> ReadStringArg(const Core::CPUThreadGuard& guar
   if (!std::isnan(num))
   {
     u32 address = static_cast<u32>(num);
-    return PowerPC::HostGetString(guard, address);
+    return PowerPC::MMU::HostGetString(guard, address);
   }
 
   const char* cstr = expr_get_str(e);
diff --git a/Source/Core/Core/PowerPC/GDBStub.cpp b/Source/Core/Core/PowerPC/GDBStub.cpp
index 94d3e2e060..7a702e7188 100644
--- a/Source/Core/Core/PowerPC/GDBStub.cpp
+++ b/Source/Core/Core/PowerPC/GDBStub.cpp
@@ -821,7 +821,7 @@ static void ReadMemory(const Core::CPUThreadGuard& guard)
   if (len * 2 > sizeof reply)
     SendReply("E01");
 
-  if (!PowerPC::HostIsRAMAddress(guard, addr))
+  if (!PowerPC::MMU::HostIsRAMAddress(guard, addr))
     return SendReply("E00");
 
   auto& system = Core::System::GetInstance();
@@ -848,7 +848,7 @@ static void WriteMemory(const Core::CPUThreadGuard& guard)
     len = (len << 4) | Hex2char(s_cmd_bfr[i++]);
   INFO_LOG_FMT(GDB_STUB, "gdb: write memory: {:08x} bytes to {:08x}", len, addr);
 
-  if (!PowerPC::HostIsRAMAddress(guard, addr))
+  if (!PowerPC::MMU::HostIsRAMAddress(guard, addr))
     return SendReply("E00");
 
   auto& system = Core::System::GetInstance();
diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
index 561367eae0..c41635c7e6 100644
--- a/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
+++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter.cpp
@@ -64,8 +64,8 @@ void Interpreter::UpdatePC()
   m_ppc_state.pc = m_ppc_state.npc;
 }
 
-Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state)
-    : m_system(system), m_ppc_state(ppc_state)
+Interpreter::Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu)
+    : m_system(system), m_ppc_state(ppc_state), m_mmu(mmu)
 {
 }
 
@@ -124,7 +124,7 @@ int Interpreter::SingleStepInner()
   }
 
   m_ppc_state.npc = m_ppc_state.pc + sizeof(UGeckoInstruction);
-  m_prev_inst.hex = PowerPC::Read_Opcode(m_ppc_state.pc);
+  m_prev_inst.hex = m_mmu.Read_Opcode(m_ppc_state.pc);
 
   const GekkoOPInfo* opinfo = PPCTables::GetOpInfo(m_prev_inst);
 
@@ -316,7 +316,7 @@ void Interpreter::unknown_instruction(Interpreter& interpreter, UGeckoInstructio
   Core::CPUThreadGuard guard(system);
 
   const u32 last_pc = interpreter.m_last_pc;
-  const u32 opcode = PowerPC::HostRead_U32(guard, last_pc);
+  const u32 opcode = PowerPC::MMU::HostRead_U32(guard, last_pc);
   const std::string disasm = Common::GekkoDisassembler::Disassemble(opcode, last_pc);
   NOTICE_LOG_FMT(POWERPC, "Last PC = {:08x} : {}", last_pc, disasm);
   Dolphin_Debugger::PrintCallstack(system, guard, Common::Log::LogType::POWERPC,
diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter.h b/Source/Core/Core/PowerPC/Interpreter/Interpreter.h
index 686557461f..90595de068 100644
--- a/Source/Core/Core/PowerPC/Interpreter/Interpreter.h
+++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter.h
@@ -15,13 +15,14 @@ class System;
 }
 namespace PowerPC
 {
+class MMU;
 struct PowerPCState;
-}
+}  // namespace PowerPC
 
 class Interpreter : public CPUCoreBase
 {
 public:
-  Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state);
+  Interpreter(Core::System& system, PowerPC::PowerPCState& ppc_state, PowerPC::MMU& mmu);
   Interpreter(const Interpreter&) = delete;
   Interpreter(Interpreter&&) = delete;
   Interpreter& operator=(const Interpreter&) = delete;
@@ -312,6 +313,7 @@ private:
 
   Core::System& m_system;
   PowerPC::PowerPCState& m_ppc_state;
+  PowerPC::MMU& m_mmu;
 
   UGeckoInstruction m_prev_inst{};
   u32 m_last_pc = 0;
diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStore.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStore.cpp
index 73b0c848b1..f046fadb98 100644
--- a/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStore.cpp
+++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStore.cpp
@@ -41,7 +41,7 @@ static u32 Helper_Get_EA_UX(const PowerPC::PowerPCState& ppcs, const UGeckoInstr
 void Interpreter::lbz(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const u32 temp = PowerPC::Read_U8(Helper_Get_EA(ppc_state, inst));
+  const u32 temp = interpreter.m_mmu.Read_U8(Helper_Get_EA(ppc_state, inst));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
     ppc_state.gpr[inst.RD] = temp;
@@ -51,7 +51,7 @@ void Interpreter::lbzu(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_U(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U8(address);
+  const u32 temp = interpreter.m_mmu.Read_U8(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -71,7 +71,7 @@ void Interpreter::lfd(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u64 temp = PowerPC::Read_U64(address);
+  const u64 temp = interpreter.m_mmu.Read_U64(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
     ppc_state.ps[inst.FD].SetPS0(temp);
@@ -88,7 +88,7 @@ void Interpreter::lfdu(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u64 temp = PowerPC::Read_U64(address);
+  const u64 temp = interpreter.m_mmu.Read_U64(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -108,7 +108,7 @@ void Interpreter::lfdux(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u64 temp = PowerPC::Read_U64(address);
+  const u64 temp = interpreter.m_mmu.Read_U64(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -128,7 +128,7 @@ void Interpreter::lfdx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u64 temp = PowerPC::Read_U64(address);
+  const u64 temp = interpreter.m_mmu.Read_U64(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
     ppc_state.ps[inst.FD].SetPS0(temp);
@@ -145,7 +145,7 @@ void Interpreter::lfs(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -165,7 +165,7 @@ void Interpreter::lfsu(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -186,7 +186,7 @@ void Interpreter::lfsux(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -207,7 +207,7 @@ void Interpreter::lfsx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -219,7 +219,7 @@ void Interpreter::lfsx(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::lha(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const u32 temp = u32(s32(s16(PowerPC::Read_U16(Helper_Get_EA(ppc_state, inst)))));
+  const u32 temp = u32(s32(s16(interpreter.m_mmu.Read_U16(Helper_Get_EA(ppc_state, inst)))));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -231,7 +231,7 @@ void Interpreter::lhau(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_U(ppc_state, inst);
-  const u32 temp = u32(s32(s16(PowerPC::Read_U16(address))));
+  const u32 temp = u32(s32(s16(interpreter.m_mmu.Read_U16(address))));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -243,7 +243,7 @@ void Interpreter::lhau(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::lhz(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const u32 temp = PowerPC::Read_U16(Helper_Get_EA(ppc_state, inst));
+  const u32 temp = interpreter.m_mmu.Read_U16(Helper_Get_EA(ppc_state, inst));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -255,7 +255,7 @@ void Interpreter::lhzu(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_U(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U16(address);
+  const u32 temp = interpreter.m_mmu.Read_U16(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -278,7 +278,7 @@ void Interpreter::lmw(Interpreter& interpreter, UGeckoInstruction inst)
 
   for (u32 i = inst.RD; i <= 31; i++, address += 4)
   {
-    const u32 temp_reg = PowerPC::Read_U32(address);
+    const u32 temp_reg = interpreter.m_mmu.Read_U32(address);
 
     if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
     {
@@ -307,7 +307,7 @@ void Interpreter::stmw(Interpreter& interpreter, UGeckoInstruction inst)
 
   for (u32 i = inst.RS; i <= 31; i++, address += 4)
   {
-    PowerPC::Write_U32(ppc_state.gpr[i], address);
+    interpreter.m_mmu.Write_U32(ppc_state.gpr[i], address);
     if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
     {
       PanicAlertFmt("DSI exception in stmw");
@@ -321,7 +321,7 @@ void Interpreter::lwz(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -333,7 +333,7 @@ void Interpreter::lwzu(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_U(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -345,7 +345,7 @@ void Interpreter::lwzu(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::stb(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  PowerPC::Write_U8(ppc_state.gpr[inst.RS], Helper_Get_EA(ppc_state, inst));
+  interpreter.m_mmu.Write_U8(ppc_state.gpr[inst.RS], Helper_Get_EA(ppc_state, inst));
 }
 
 void Interpreter::stbu(Interpreter& interpreter, UGeckoInstruction inst)
@@ -353,7 +353,7 @@ void Interpreter::stbu(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_U(ppc_state, inst);
 
-  PowerPC::Write_U8(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U8(ppc_state.gpr[inst.RS], address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -371,7 +371,7 @@ void Interpreter::stfd(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
+  interpreter.m_mmu.Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
 }
 
 void Interpreter::stfdu(Interpreter& interpreter, UGeckoInstruction inst)
@@ -385,7 +385,7 @@ void Interpreter::stfdu(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
+  interpreter.m_mmu.Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -403,7 +403,7 @@ void Interpreter::stfs(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
+  interpreter.m_mmu.Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
 }
 
 void Interpreter::stfsu(Interpreter& interpreter, UGeckoInstruction inst)
@@ -417,7 +417,7 @@ void Interpreter::stfsu(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
+  interpreter.m_mmu.Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -427,7 +427,7 @@ void Interpreter::stfsu(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::sth(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  PowerPC::Write_U16(ppc_state.gpr[inst.RS], Helper_Get_EA(ppc_state, inst));
+  interpreter.m_mmu.Write_U16(ppc_state.gpr[inst.RS], Helper_Get_EA(ppc_state, inst));
 }
 
 void Interpreter::sthu(Interpreter& interpreter, UGeckoInstruction inst)
@@ -435,7 +435,7 @@ void Interpreter::sthu(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_U(ppc_state, inst);
 
-  PowerPC::Write_U16(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U16(ppc_state.gpr[inst.RS], address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -445,7 +445,7 @@ void Interpreter::sthu(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::stw(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  PowerPC::Write_U32(ppc_state.gpr[inst.RS], Helper_Get_EA(ppc_state, inst));
+  interpreter.m_mmu.Write_U32(ppc_state.gpr[inst.RS], Helper_Get_EA(ppc_state, inst));
 }
 
 void Interpreter::stwu(Interpreter& interpreter, UGeckoInstruction inst)
@@ -453,7 +453,7 @@ void Interpreter::stwu(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_U(ppc_state, inst);
 
-  PowerPC::Write_U32(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U32(ppc_state.gpr[inst.RS], address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -478,7 +478,7 @@ void Interpreter::dcbf(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::FlushDCacheLine(address);
+  interpreter.m_mmu.FlushDCacheLine(address);
 }
 
 void Interpreter::dcbi(Interpreter& interpreter, UGeckoInstruction inst)
@@ -500,7 +500,7 @@ void Interpreter::dcbi(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::InvalidateDCacheLine(address);
+  interpreter.m_mmu.InvalidateDCacheLine(address);
 }
 
 void Interpreter::dcbst(Interpreter& interpreter, UGeckoInstruction inst)
@@ -516,7 +516,7 @@ void Interpreter::dcbst(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::StoreDCacheLine(address);
+  interpreter.m_mmu.StoreDCacheLine(address);
 }
 
 // These instructions hint that it might be optimal to prefetch the specified cache line into the
@@ -553,7 +553,7 @@ void Interpreter::dcbz(Interpreter& interpreter, UGeckoInstruction inst)
     }
   }
 
-  PowerPC::ClearDCacheLine(dcbz_addr & (~31));
+  interpreter.m_mmu.ClearDCacheLine(dcbz_addr & (~31));
 }
 
 void Interpreter::dcbz_l(Interpreter& interpreter, UGeckoInstruction inst)
@@ -573,7 +573,7 @@ void Interpreter::dcbz_l(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::ClearDCacheLine(address & (~31));
+  interpreter.m_mmu.ClearDCacheLine(address & (~31));
 }
 
 // eciwx/ecowx technically should access the specified device
@@ -595,7 +595,7 @@ void Interpreter::eciwx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  ppc_state.gpr[inst.RD] = PowerPC::Read_U32(EA);
+  ppc_state.gpr[inst.RD] = interpreter.m_mmu.Read_U32(EA);
 }
 
 void Interpreter::ecowx(Interpreter& interpreter, UGeckoInstruction inst)
@@ -615,7 +615,7 @@ void Interpreter::ecowx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U32(ppc_state.gpr[inst.RS], EA);
+  interpreter.m_mmu.Write_U32(ppc_state.gpr[inst.RS], EA);
 }
 
 void Interpreter::eieio(Interpreter& interpreter, UGeckoInstruction inst)
@@ -638,7 +638,7 @@ void Interpreter::lbzux(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_UX(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U8(address);
+  const u32 temp = interpreter.m_mmu.Read_U8(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -650,7 +650,7 @@ void Interpreter::lbzux(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::lbzx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const u32 temp = PowerPC::Read_U8(Helper_Get_EA_X(ppc_state, inst));
+  const u32 temp = interpreter.m_mmu.Read_U8(Helper_Get_EA_X(ppc_state, inst));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -662,7 +662,7 @@ void Interpreter::lhaux(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_UX(ppc_state, inst);
-  const s32 temp = s32{s16(PowerPC::Read_U16(address))};
+  const s32 temp = s32{s16(interpreter.m_mmu.Read_U16(address))};
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -674,7 +674,7 @@ void Interpreter::lhaux(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::lhax(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const s32 temp = s32{s16(PowerPC::Read_U16(Helper_Get_EA_X(ppc_state, inst)))};
+  const s32 temp = s32{s16(interpreter.m_mmu.Read_U16(Helper_Get_EA_X(ppc_state, inst)))};
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -685,7 +685,7 @@ void Interpreter::lhax(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::lhbrx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const u32 temp = Common::swap16(PowerPC::Read_U16(Helper_Get_EA_X(ppc_state, inst)));
+  const u32 temp = Common::swap16(interpreter.m_mmu.Read_U16(Helper_Get_EA_X(ppc_state, inst)));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -697,7 +697,7 @@ void Interpreter::lhzux(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_UX(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U16(address);
+  const u32 temp = interpreter.m_mmu.Read_U16(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -709,7 +709,7 @@ void Interpreter::lhzux(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::lhzx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const u32 temp = PowerPC::Read_U16(Helper_Get_EA_X(ppc_state, inst));
+  const u32 temp = interpreter.m_mmu.Read_U16(Helper_Get_EA_X(ppc_state, inst));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -738,7 +738,7 @@ void Interpreter::lswx(Interpreter& interpreter, UGeckoInstruction inst)
     if ((n & 0b11) == 0)
       ppc_state.gpr[reg] = 0;
 
-    const u32 temp_value = PowerPC::Read_U8(EA) << (24 - offset);
+    const u32 temp_value = interpreter.m_mmu.Read_U8(EA) << (24 - offset);
     // Not64 (Homebrew N64 Emulator for Wii) triggers the following case.
     if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
     {
@@ -754,7 +754,7 @@ void Interpreter::lswx(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::lwbrx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  const u32 temp = Common::swap32(PowerPC::Read_U32(Helper_Get_EA_X(ppc_state, inst)));
+  const u32 temp = Common::swap32(interpreter.m_mmu.Read_U32(Helper_Get_EA_X(ppc_state, inst)));
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -766,7 +766,7 @@ void Interpreter::lwzux(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_UX(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -779,7 +779,7 @@ void Interpreter::lwzx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_X(ppc_state, inst);
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -792,7 +792,7 @@ void Interpreter::stbux(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_UX(ppc_state, inst);
 
-  PowerPC::Write_U8(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U8(ppc_state.gpr[inst.RS], address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -802,7 +802,7 @@ void Interpreter::stbux(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::stbx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  PowerPC::Write_U8(ppc_state.gpr[inst.RS], Helper_Get_EA_X(ppc_state, inst));
+  interpreter.m_mmu.Write_U8(ppc_state.gpr[inst.RS], Helper_Get_EA_X(ppc_state, inst));
 }
 
 void Interpreter::stfdux(Interpreter& interpreter, UGeckoInstruction inst)
@@ -816,7 +816,7 @@ void Interpreter::stfdux(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
+  interpreter.m_mmu.Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -834,7 +834,7 @@ void Interpreter::stfdx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
+  interpreter.m_mmu.Write_U64(ppc_state.ps[inst.FS].PS0AsU64(), address);
 }
 
 // Stores Floating points into Integers indeXed
@@ -849,7 +849,7 @@ void Interpreter::stfiwx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U32(ppc_state.ps[inst.FS].PS0AsU32(), address);
+  interpreter.m_mmu.Write_U32(ppc_state.ps[inst.FS].PS0AsU32(), address);
 }
 
 void Interpreter::stfsux(Interpreter& interpreter, UGeckoInstruction inst)
@@ -863,7 +863,7 @@ void Interpreter::stfsux(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
+  interpreter.m_mmu.Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -881,13 +881,13 @@ void Interpreter::stfsx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  PowerPC::Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
+  interpreter.m_mmu.Write_U32(ConvertToSingle(ppc_state.ps[inst.FS].PS0AsU64()), address);
 }
 
 void Interpreter::sthbrx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  PowerPC::Write_U16_Swap(ppc_state.gpr[inst.RS], Helper_Get_EA_X(ppc_state, inst));
+  interpreter.m_mmu.Write_U16_Swap(ppc_state.gpr[inst.RS], Helper_Get_EA_X(ppc_state, inst));
 }
 
 void Interpreter::sthux(Interpreter& interpreter, UGeckoInstruction inst)
@@ -895,7 +895,7 @@ void Interpreter::sthux(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_UX(ppc_state, inst);
 
-  PowerPC::Write_U16(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U16(ppc_state.gpr[inst.RS], address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -905,7 +905,7 @@ void Interpreter::sthux(Interpreter& interpreter, UGeckoInstruction inst)
 void Interpreter::sthx(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
-  PowerPC::Write_U16(ppc_state.gpr[inst.RS], Helper_Get_EA_X(ppc_state, inst));
+  interpreter.m_mmu.Write_U16(ppc_state.gpr[inst.RS], Helper_Get_EA_X(ppc_state, inst));
 }
 
 // lswi - bizarro string instruction
@@ -938,7 +938,7 @@ void Interpreter::lswi(Interpreter& interpreter, UGeckoInstruction inst)
       ppc_state.gpr[r] = 0;
     }
 
-    const u32 temp_value = PowerPC::Read_U8(EA) << (24 - i);
+    const u32 temp_value = interpreter.m_mmu.Read_U8(EA) << (24 - i);
     if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
     {
       PanicAlertFmt("DSI exception in lsw.");
@@ -984,7 +984,7 @@ void Interpreter::stswi(Interpreter& interpreter, UGeckoInstruction inst)
       r++;
       r &= 31;
     }
-    PowerPC::Write_U8((ppc_state.gpr[r] >> (24 - i)) & 0xFF, EA);
+    interpreter.m_mmu.Write_U8((ppc_state.gpr[r] >> (24 - i)) & 0xFF, EA);
     if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
     {
       return;
@@ -1016,7 +1016,7 @@ void Interpreter::stswx(Interpreter& interpreter, UGeckoInstruction inst)
 
   while (n > 0)
   {
-    PowerPC::Write_U8((ppc_state.gpr[r] >> (24 - i)) & 0xFF, EA);
+    interpreter.m_mmu.Write_U8((ppc_state.gpr[r] >> (24 - i)) & 0xFF, EA);
 
     EA++;
     n--;
@@ -1034,7 +1034,7 @@ void Interpreter::stwbrx(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_X(ppc_state, inst);
 
-  PowerPC::Write_U32_Swap(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U32_Swap(ppc_state.gpr[inst.RS], address);
 }
 
 // The following two instructions are for SMP communications. On a single
@@ -1051,7 +1051,7 @@ void Interpreter::lwarx(Interpreter& interpreter, UGeckoInstruction inst)
     return;
   }
 
-  const u32 temp = PowerPC::Read_U32(address);
+  const u32 temp = interpreter.m_mmu.Read_U32(address);
 
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
@@ -1077,7 +1077,7 @@ void Interpreter::stwcxd(Interpreter& interpreter, UGeckoInstruction inst)
   {
     if (address == ppc_state.reserve_address)
     {
-      PowerPC::Write_U32(ppc_state.gpr[inst.RS], address);
+      interpreter.m_mmu.Write_U32(ppc_state.gpr[inst.RS], address);
       if (!(ppc_state.Exceptions & EXCEPTION_DSI))
       {
         ppc_state.reserve = false;
@@ -1095,7 +1095,7 @@ void Interpreter::stwux(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_UX(ppc_state, inst);
 
-  PowerPC::Write_U32(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U32(ppc_state.gpr[inst.RS], address);
   if (!(ppc_state.Exceptions & EXCEPTION_DSI))
   {
     ppc_state.gpr[inst.RA] = address;
@@ -1107,7 +1107,7 @@ void Interpreter::stwx(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 address = Helper_Get_EA_X(ppc_state, inst);
 
-  PowerPC::Write_U32(ppc_state.gpr[inst.RS], address);
+  interpreter.m_mmu.Write_U32(ppc_state.gpr[inst.RS], address);
 }
 
 void Interpreter::sync(Interpreter& interpreter, UGeckoInstruction inst)
@@ -1127,7 +1127,7 @@ void Interpreter::tlbie(Interpreter& interpreter, UGeckoInstruction inst)
   // Invalidate TLB entry
   const u32 address = ppc_state.gpr[inst.RB];
 
-  PowerPC::InvalidateTLBEntry(address);
+  interpreter.m_mmu.InvalidateTLBEntry(address);
 }
 
 void Interpreter::tlbsync(Interpreter& interpreter, UGeckoInstruction inst)
diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStorePaired.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStorePaired.cpp
index 11e0cc178c..29f0561033 100644
--- a/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStorePaired.cpp
+++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter_LoadStorePaired.cpp
@@ -16,6 +16,7 @@
 #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h"
 #include "Core/PowerPC/MMU.h"
 #include "Core/PowerPC/PowerPC.h"
+#include "Core/System.h"
 
 // dequantize table
 const float m_dequantizeTable[] = {
@@ -68,111 +69,111 @@ SType ScaleAndClamp(double ps, u32 st_scale)
 }
 
 template <typename T>
-static T ReadUnpaired(u32 addr);
+static T ReadUnpaired(PowerPC::MMU& mmu, u32 addr);
 
 template <>
-u8 ReadUnpaired<u8>(u32 addr)
+u8 ReadUnpaired<u8>(PowerPC::MMU& mmu, u32 addr)
 {
-  return PowerPC::Read_U8(addr);
+  return mmu.Read_U8(addr);
 }
 
 template <>
-u16 ReadUnpaired<u16>(u32 addr)
+u16 ReadUnpaired<u16>(PowerPC::MMU& mmu, u32 addr)
 {
-  return PowerPC::Read_U16(addr);
+  return mmu.Read_U16(addr);
 }
 
 template <>
-u32 ReadUnpaired<u32>(u32 addr)
+u32 ReadUnpaired<u32>(PowerPC::MMU& mmu, u32 addr)
 {
-  return PowerPC::Read_U32(addr);
+  return mmu.Read_U32(addr);
 }
 
 template <typename T>
-static std::pair<T, T> ReadPair(u32 addr);
+static std::pair<T, T> ReadPair(PowerPC::MMU& mmu, u32 addr);
 
 template <>
-std::pair<u8, u8> ReadPair<u8>(u32 addr)
+std::pair<u8, u8> ReadPair<u8>(PowerPC::MMU& mmu, u32 addr)
 {
-  const u16 val = PowerPC::Read_U16(addr);
+  const u16 val = mmu.Read_U16(addr);
   return {u8(val >> 8), u8(val)};
 }
 
 template <>
-std::pair<u16, u16> ReadPair<u16>(u32 addr)
+std::pair<u16, u16> ReadPair<u16>(PowerPC::MMU& mmu, u32 addr)
 {
-  const u32 val = PowerPC::Read_U32(addr);
+  const u32 val = mmu.Read_U32(addr);
   return {u16(val >> 16), u16(val)};
 }
 
 template <>
-std::pair<u32, u32> ReadPair<u32>(u32 addr)
+std::pair<u32, u32> ReadPair<u32>(PowerPC::MMU& mmu, u32 addr)
 {
-  const u64 val = PowerPC::Read_U64(addr);
+  const u64 val = mmu.Read_U64(addr);
   return {u32(val >> 32), u32(val)};
 }
 
 template <typename T>
-static void WriteUnpaired(T val, u32 addr);
+static void WriteUnpaired(PowerPC::MMU& mmu, T val, u32 addr);
 
 template <>
-void WriteUnpaired<u8>(u8 val, u32 addr)
+void WriteUnpaired<u8>(PowerPC::MMU& mmu, u8 val, u32 addr)
 {
-  PowerPC::Write_U8(val, addr);
+  mmu.Write_U8(val, addr);
 }
 
 template <>
-void WriteUnpaired<u16>(u16 val, u32 addr)
+void WriteUnpaired<u16>(PowerPC::MMU& mmu, u16 val, u32 addr)
 {
-  PowerPC::Write_U16(val, addr);
+  mmu.Write_U16(val, addr);
 }
 
 template <>
-void WriteUnpaired<u32>(u32 val, u32 addr)
+void WriteUnpaired<u32>(PowerPC::MMU& mmu, u32 val, u32 addr)
 {
-  PowerPC::Write_U32(val, addr);
+  mmu.Write_U32(val, addr);
 }
 
 template <typename T>
-static void WritePair(T val1, T val2, u32 addr);
+static void WritePair(PowerPC::MMU& mmu, T val1, T val2, u32 addr);
 
 template <>
-void WritePair<u8>(u8 val1, u8 val2, u32 addr)
+void WritePair<u8>(PowerPC::MMU& mmu, u8 val1, u8 val2, u32 addr)
 {
-  PowerPC::Write_U16((u16{val1} << 8) | u16{val2}, addr);
+  mmu.Write_U16((u16{val1} << 8) | u16{val2}, addr);
 }
 
 template <>
-void WritePair<u16>(u16 val1, u16 val2, u32 addr)
+void WritePair<u16>(PowerPC::MMU& mmu, u16 val1, u16 val2, u32 addr)
 {
-  PowerPC::Write_U32((u32{val1} << 16) | u32{val2}, addr);
+  mmu.Write_U32((u32{val1} << 16) | u32{val2}, addr);
 }
 
 template <>
-void WritePair<u32>(u32 val1, u32 val2, u32 addr)
+void WritePair<u32>(PowerPC::MMU& mmu, u32 val1, u32 val2, u32 addr)
 {
-  PowerPC::Write_U64((u64{val1} << 32) | u64{val2}, addr);
+  mmu.Write_U64((u64{val1} << 32) | u64{val2}, addr);
 }
 
 template <typename T>
-void QuantizeAndStore(double ps0, double ps1, u32 addr, u32 instW, u32 st_scale)
+void QuantizeAndStore(PowerPC::MMU& mmu, double ps0, double ps1, u32 addr, u32 instW, u32 st_scale)
 {
   using U = std::make_unsigned_t<T>;
 
   const U conv_ps0 = U(ScaleAndClamp<T>(ps0, st_scale));
   if (instW)
   {
-    WriteUnpaired<U>(conv_ps0, addr);
+    WriteUnpaired<U>(mmu, conv_ps0, addr);
   }
   else
   {
     const U conv_ps1 = U(ScaleAndClamp<T>(ps1, st_scale));
-    WritePair<U>(conv_ps0, conv_ps1, addr);
+    WritePair<U>(mmu, conv_ps0, conv_ps1, addr);
   }
 }
 
-static void Helper_Quantize(const PowerPC::PowerPCState* ppcs, u32 addr, u32 instI, u32 instRS,
-                            u32 instW)
+static void Helper_Quantize(PowerPC::MMU& mmu, const PowerPC::PowerPCState* ppcs, u32 addr,
+                            u32 instI, u32 instRS, u32 instW)
 {
   const UGQR gqr(ppcs->spr[SPR_GQR0 + instI]);
   const EQuantizeType st_type = gqr.st_type;
@@ -190,32 +191,32 @@ static void Helper_Quantize(const PowerPC::PowerPCState* ppcs, u32 addr, u32 ins
 
     if (instW != 0)
     {
-      WriteUnpaired<u32>(conv_ps0, addr);
+      WriteUnpaired<u32>(mmu, conv_ps0, addr);
     }
     else
     {
       const u64 integral_ps1 = Common::BitCast<u64>(ps1);
       const u32 conv_ps1 = ConvertToSingleFTZ(integral_ps1);
 
-      WritePair<u32>(conv_ps0, conv_ps1, addr);
+      WritePair<u32>(mmu, conv_ps0, conv_ps1, addr);
     }
     break;
   }
 
   case QUANTIZE_U8:
-    QuantizeAndStore<u8>(ps0, ps1, addr, instW, st_scale);
+    QuantizeAndStore<u8>(mmu, ps0, ps1, addr, instW, st_scale);
     break;
 
   case QUANTIZE_U16:
-    QuantizeAndStore<u16>(ps0, ps1, addr, instW, st_scale);
+    QuantizeAndStore<u16>(mmu, ps0, ps1, addr, instW, st_scale);
     break;
 
   case QUANTIZE_S8:
-    QuantizeAndStore<s8>(ps0, ps1, addr, instW, st_scale);
+    QuantizeAndStore<s8>(mmu, ps0, ps1, addr, instW, st_scale);
     break;
 
   case QUANTIZE_S16:
-    QuantizeAndStore<s16>(ps0, ps1, addr, instW, st_scale);
+    QuantizeAndStore<s16>(mmu, ps0, ps1, addr, instW, st_scale);
     break;
 
   case QUANTIZE_INVALID1:
@@ -227,20 +228,20 @@ static void Helper_Quantize(const PowerPC::PowerPCState* ppcs, u32 addr, u32 ins
 }
 
 template <typename T>
-std::pair<double, double> LoadAndDequantize(u32 addr, u32 instW, u32 ld_scale)
+std::pair<double, double> LoadAndDequantize(PowerPC::MMU& mmu, u32 addr, u32 instW, u32 ld_scale)
 {
   using U = std::make_unsigned_t<T>;
 
   float ps0, ps1;
   if (instW != 0)
   {
-    const U value = ReadUnpaired<U>(addr);
+    const U value = ReadUnpaired<U>(mmu, addr);
     ps0 = float(T(value)) * m_dequantizeTable[ld_scale];
     ps1 = 1.0f;
   }
   else
   {
-    const auto [first, second] = ReadPair<U>(addr);
+    const auto [first, second] = ReadPair<U>(mmu, addr);
     ps0 = float(T(first)) * m_dequantizeTable[ld_scale];
     ps1 = float(T(second)) * m_dequantizeTable[ld_scale];
   }
@@ -248,8 +249,8 @@ std::pair<double, double> LoadAndDequantize(u32 addr, u32 instW, u32 ld_scale)
   return {static_cast<double>(ps0), static_cast<double>(ps1)};
 }
 
-static void Helper_Dequantize(PowerPC::PowerPCState* ppcs, u32 addr, u32 instI, u32 instRD,
-                              u32 instW)
+static void Helper_Dequantize(PowerPC::MMU& mmu, PowerPC::PowerPCState* ppcs, u32 addr, u32 instI,
+                              u32 instRD, u32 instW)
 {
   const UGQR gqr(ppcs->spr[SPR_GQR0 + instI]);
   const EQuantizeType ld_type = gqr.ld_type;
@@ -263,32 +264,32 @@ static void Helper_Dequantize(PowerPC::PowerPCState* ppcs, u32 addr, u32 instI,
   case QUANTIZE_FLOAT:
     if (instW != 0)
     {
-      const u32 value = ReadUnpaired<u32>(addr);
+      const u32 value = ReadUnpaired<u32>(mmu, addr);
       ps0 = Common::BitCast<double>(ConvertToDouble(value));
       ps1 = 1.0;
     }
     else
     {
-      const auto [first, second] = ReadPair<u32>(addr);
+      const auto [first, second] = ReadPair<u32>(mmu, addr);
       ps0 = Common::BitCast<double>(ConvertToDouble(first));
       ps1 = Common::BitCast<double>(ConvertToDouble(second));
     }
     break;
 
   case QUANTIZE_U8:
-    std::tie(ps0, ps1) = LoadAndDequantize<u8>(addr, instW, ld_scale);
+    std::tie(ps0, ps1) = LoadAndDequantize<u8>(mmu, addr, instW, ld_scale);
     break;
 
   case QUANTIZE_U16:
-    std::tie(ps0, ps1) = LoadAndDequantize<u16>(addr, instW, ld_scale);
+    std::tie(ps0, ps1) = LoadAndDequantize<u16>(mmu, addr, instW, ld_scale);
     break;
 
   case QUANTIZE_S8:
-    std::tie(ps0, ps1) = LoadAndDequantize<s8>(addr, instW, ld_scale);
+    std::tie(ps0, ps1) = LoadAndDequantize<s8>(mmu, addr, instW, ld_scale);
     break;
 
   case QUANTIZE_S16:
-    std::tie(ps0, ps1) = LoadAndDequantize<s16>(addr, instW, ld_scale);
+    std::tie(ps0, ps1) = LoadAndDequantize<s16>(mmu, addr, instW, ld_scale);
     break;
 
   case QUANTIZE_INVALID1:
@@ -318,7 +319,7 @@ void Interpreter::psq_l(Interpreter& interpreter, UGeckoInstruction inst)
   }
 
   const u32 EA = inst.RA ? (ppc_state.gpr[inst.RA] + u32(inst.SIMM_12)) : u32(inst.SIMM_12);
-  Helper_Dequantize(&ppc_state, EA, inst.I, inst.RD, inst.W);
+  Helper_Dequantize(interpreter.m_mmu, &ppc_state, EA, inst.I, inst.RD, inst.W);
 }
 
 void Interpreter::psq_lu(Interpreter& interpreter, UGeckoInstruction inst)
@@ -331,7 +332,7 @@ void Interpreter::psq_lu(Interpreter& interpreter, UGeckoInstruction inst)
   }
 
   const u32 EA = ppc_state.gpr[inst.RA] + u32(inst.SIMM_12);
-  Helper_Dequantize(&ppc_state, EA, inst.I, inst.RD, inst.W);
+  Helper_Dequantize(interpreter.m_mmu, &ppc_state, EA, inst.I, inst.RD, inst.W);
 
   if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
   {
@@ -351,7 +352,7 @@ void Interpreter::psq_st(Interpreter& interpreter, UGeckoInstruction inst)
   }
 
   const u32 EA = inst.RA ? (ppc_state.gpr[inst.RA] + u32(inst.SIMM_12)) : u32(inst.SIMM_12);
-  Helper_Quantize(&ppc_state, EA, inst.I, inst.RS, inst.W);
+  Helper_Quantize(interpreter.m_mmu, &ppc_state, EA, inst.I, inst.RS, inst.W);
 }
 
 void Interpreter::psq_stu(Interpreter& interpreter, UGeckoInstruction inst)
@@ -364,7 +365,7 @@ void Interpreter::psq_stu(Interpreter& interpreter, UGeckoInstruction inst)
   }
 
   const u32 EA = ppc_state.gpr[inst.RA] + u32(inst.SIMM_12);
-  Helper_Quantize(&ppc_state, EA, inst.I, inst.RS, inst.W);
+  Helper_Quantize(interpreter.m_mmu, &ppc_state, EA, inst.I, inst.RS, inst.W);
 
   if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
   {
@@ -379,7 +380,7 @@ void Interpreter::psq_lx(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 EA =
       inst.RA ? (ppc_state.gpr[inst.RA] + ppc_state.gpr[inst.RB]) : ppc_state.gpr[inst.RB];
-  Helper_Dequantize(&ppc_state, EA, inst.Ix, inst.RD, inst.Wx);
+  Helper_Dequantize(interpreter.m_mmu, &ppc_state, EA, inst.Ix, inst.RD, inst.Wx);
 }
 
 void Interpreter::psq_stx(Interpreter& interpreter, UGeckoInstruction inst)
@@ -387,14 +388,14 @@ void Interpreter::psq_stx(Interpreter& interpreter, UGeckoInstruction inst)
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 EA =
       inst.RA ? (ppc_state.gpr[inst.RA] + ppc_state.gpr[inst.RB]) : ppc_state.gpr[inst.RB];
-  Helper_Quantize(&ppc_state, EA, inst.Ix, inst.RS, inst.Wx);
+  Helper_Quantize(interpreter.m_mmu, &ppc_state, EA, inst.Ix, inst.RS, inst.Wx);
 }
 
 void Interpreter::psq_lux(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 EA = ppc_state.gpr[inst.RA] + ppc_state.gpr[inst.RB];
-  Helper_Dequantize(&ppc_state, EA, inst.Ix, inst.RD, inst.Wx);
+  Helper_Dequantize(interpreter.m_mmu, &ppc_state, EA, inst.Ix, inst.RD, inst.Wx);
 
   if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
   {
@@ -408,7 +409,7 @@ void Interpreter::psq_stux(Interpreter& interpreter, UGeckoInstruction inst)
 {
   auto& ppc_state = interpreter.m_ppc_state;
   const u32 EA = ppc_state.gpr[inst.RA] + ppc_state.gpr[inst.RB];
-  Helper_Quantize(&ppc_state, EA, inst.Ix, inst.RS, inst.Wx);
+  Helper_Quantize(interpreter.m_mmu, &ppc_state, EA, inst.Ix, inst.RS, inst.Wx);
 
   if ((ppc_state.Exceptions & EXCEPTION_DSI) != 0)
   {
diff --git a/Source/Core/Core/PowerPC/Interpreter/Interpreter_SystemRegisters.cpp b/Source/Core/Core/PowerPC/Interpreter/Interpreter_SystemRegisters.cpp
index 91c66e08db..88314990da 100644
--- a/Source/Core/Core/PowerPC/Interpreter/Interpreter_SystemRegisters.cpp
+++ b/Source/Core/Core/PowerPC/Interpreter/Interpreter_SystemRegisters.cpp
@@ -379,8 +379,8 @@ void Interpreter::mtspr(Interpreter& interpreter, UGeckoInstruction inst)
     if (old_value != ppc_state.spr[index])
     {
       INFO_LOG_FMT(POWERPC, "HID4 updated {:x} {:x}", old_value, ppc_state.spr[index]);
-      PowerPC::IBATUpdated();
-      PowerPC::DBATUpdated();
+      interpreter.m_mmu.IBATUpdated();
+      interpreter.m_mmu.DBATUpdated();
     }
     break;
 
@@ -414,9 +414,9 @@ void Interpreter::mtspr(Interpreter& interpreter, UGeckoInstruction inst)
       if (length == 0)
         length = 128;
       if (DMAL(ppc_state).DMA_LD)
-        PowerPC::DMA_MemoryToLC(cache_address, mem_address, length);
+        interpreter.m_mmu.DMA_MemoryToLC(cache_address, mem_address, length);
       else
-        PowerPC::DMA_LCToMemory(mem_address, cache_address, length);
+        interpreter.m_mmu.DMA_LCToMemory(mem_address, cache_address, length);
     }
     DMAL(ppc_state).DMA_T = 0;
     break;
@@ -436,7 +436,7 @@ void Interpreter::mtspr(Interpreter& interpreter, UGeckoInstruction inst)
 
   // Page table base etc
   case SPR_SDR:
-    PowerPC::SDRUpdated();
+    interpreter.m_mmu.SDRUpdated();
     break;
 
   case SPR_XER:
@@ -462,7 +462,7 @@ void Interpreter::mtspr(Interpreter& interpreter, UGeckoInstruction inst)
     if (old_value != ppc_state.spr[index])
     {
       INFO_LOG_FMT(POWERPC, "DBAT updated {} {:x} {:x}", index, old_value, ppc_state.spr[index]);
-      PowerPC::DBATUpdated();
+      interpreter.m_mmu.DBATUpdated();
     }
     break;
 
@@ -485,7 +485,7 @@ void Interpreter::mtspr(Interpreter& interpreter, UGeckoInstruction inst)
     if (old_value != ppc_state.spr[index])
     {
       INFO_LOG_FMT(POWERPC, "IBAT updated {} {:x} {:x}", index, old_value, ppc_state.spr[index]);
-      PowerPC::IBATUpdated();
+      interpreter.m_mmu.IBATUpdated();
     }
     break;
 
diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
index 7783f58c78..145a2c9d7e 100644
--- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp
@@ -1183,8 +1183,8 @@ void Jit64::IntializeSpeculativeConstants()
   for (auto i : code_block.m_gpr_inputs)
   {
     u32 compileTimeValue = m_ppc_state.gpr[i];
-    if (PowerPC::IsOptimizableGatherPipeWrite(compileTimeValue) ||
-        PowerPC::IsOptimizableGatherPipeWrite(compileTimeValue - 0x8000) ||
+    if (m_mmu.IsOptimizableGatherPipeWrite(compileTimeValue) ||
+        m_mmu.IsOptimizableGatherPipeWrite(compileTimeValue - 0x8000) ||
         compileTimeValue == 0xCC000000)
     {
       if (!target)
diff --git a/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp b/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp
index ca287c80cc..65ac5dab21 100644
--- a/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp
+++ b/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp
@@ -324,7 +324,7 @@ void Jit64::dcbx(UGeckoInstruction inst)
   if (m_ppc_state.msr.IR)
   {
     // Translate effective address to physical address.
-    bat_lookup_failed = BATAddressLookup(addr, tmp, PowerPC::ibat_table.data());
+    bat_lookup_failed = BATAddressLookup(addr, tmp, m_jit.m_mmu.GetIBATTable().data());
     MOV(32, R(tmp), R(effective_address));
     AND(32, R(tmp), Imm32(0x0001ffff));
     AND(32, R(addr), Imm32(0xfffe0000));
@@ -430,7 +430,7 @@ void Jit64::dcbz(UGeckoInstruction inst)
   if (emit_fast_path)
   {
     // Perform lookup to see if we can use fast path.
-    MOV(64, R(RSCRATCH2), ImmPtr(&PowerPC::dbat_table[0]));
+    MOV(64, R(RSCRATCH2), ImmPtr(m_mmu.GetDBATTable().data()));
     PUSH(RSCRATCH);
     SHR(32, R(RSCRATCH), Imm8(PowerPC::BAT_INDEX_SHIFT));
     TEST(32, MComplex(RSCRATCH2, RSCRATCH, SCALE_4, 0), Imm32(PowerPC::BAT_PHYSICAL_BIT));
@@ -449,7 +449,7 @@ void Jit64::dcbz(UGeckoInstruction inst)
   MOV(32, PPCSTATE(pc), Imm32(js.compilerPC));
   BitSet32 registersInUse = CallerSavedRegistersInUse();
   ABI_PushRegistersAndAdjustStack(registersInUse, 0);
-  ABI_CallFunctionR(PowerPC::ClearDCacheLine, RSCRATCH);
+  ABI_CallFunctionPR(PowerPC::ClearDCacheLineFromJit64, &m_mmu, RSCRATCH);
   ABI_PopRegistersAndAdjustStack(registersInUse, 0);
 
   if (emit_fast_path)
diff --git a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp
index d767454b14..006e3d6227 100644
--- a/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp
+++ b/Source/Core/Core/PowerPC/Jit64Common/EmuCodeBlock.cpp
@@ -119,7 +119,7 @@ FixupBranch EmuCodeBlock::CheckIfSafeAddress(const OpArg& reg_value, X64Reg reg_
     MOV(32, R(RSCRATCH_EXTRA), R(reg_addr));
 
   // Perform lookup to see if we can use fast path.
-  MOV(64, R(RSCRATCH), ImmPtr(&PowerPC::dbat_table[0]));
+  MOV(64, R(RSCRATCH), ImmPtr(m_jit.m_mmu.GetDBATTable().data()));
   SHR(32, R(RSCRATCH_EXTRA), Imm8(PowerPC::BAT_INDEX_SHIFT));
   TEST(32, MComplex(RSCRATCH, RSCRATCH_EXTRA, SCALE_4, 0), Imm32(PowerPC::BAT_PHYSICAL_BIT));
 
@@ -396,16 +396,16 @@ void EmuCodeBlock::SafeLoadToReg(X64Reg reg_value, const Gen::OpArg& opAddress,
   switch (accessSize)
   {
   case 64:
-    ABI_CallFunctionR(PowerPC::Read_U64, reg_addr);
+    ABI_CallFunctionPR(PowerPC::ReadU64FromJit64, &m_jit.m_mmu, reg_addr);
     break;
   case 32:
-    ABI_CallFunctionR(PowerPC::Read_U32, reg_addr);
+    ABI_CallFunctionPR(PowerPC::ReadU32FromJit64, &m_jit.m_mmu, reg_addr);
     break;
   case 16:
-    ABI_CallFunctionR(PowerPC::Read_U16_ZX, reg_addr);
+    ABI_CallFunctionPR(PowerPC::ReadU16ZXFromJit64, &m_jit.m_mmu, reg_addr);
     break;
   case 8:
-    ABI_CallFunctionR(PowerPC::Read_U8_ZX, reg_addr);
+    ABI_CallFunctionPR(PowerPC::ReadU8ZXFromJit64, &m_jit.m_mmu, reg_addr);
     break;
   }
   ABI_PopRegistersAndAdjustStack(registersInUse, rsp_alignment);
@@ -436,14 +436,14 @@ void EmuCodeBlock::SafeLoadToRegImmediate(X64Reg reg_value, u32 address, int acc
                                           BitSet32 registersInUse, bool signExtend)
 {
   // If the address is known to be RAM, just load it directly.
-  if (m_jit.jo.fastmem_arena && PowerPC::IsOptimizableRAMAddress(address))
+  if (m_jit.jo.fastmem_arena && m_jit.m_mmu.IsOptimizableRAMAddress(address))
   {
     UnsafeLoadToReg(reg_value, Imm32(address), accessSize, 0, signExtend);
     return;
   }
 
   // If the address maps to an MMIO register, inline MMIO read code.
-  u32 mmioAddress = PowerPC::IsOptimizableMMIOAccess(address, accessSize);
+  u32 mmioAddress = m_jit.m_mmu.IsOptimizableMMIOAccess(address, accessSize);
   if (accessSize != 64 && mmioAddress)
   {
     auto& memory = m_jit.m_system.GetMemory();
@@ -460,16 +460,16 @@ void EmuCodeBlock::SafeLoadToRegImmediate(X64Reg reg_value, u32 address, int acc
   switch (accessSize)
   {
   case 64:
-    ABI_CallFunctionC(PowerPC::Read_U64, address);
+    ABI_CallFunctionPC(PowerPC::ReadU64FromJit64, &m_jit.m_mmu, address);
     break;
   case 32:
-    ABI_CallFunctionC(PowerPC::Read_U32, address);
+    ABI_CallFunctionPC(PowerPC::ReadU32FromJit64, &m_jit.m_mmu, address);
     break;
   case 16:
-    ABI_CallFunctionC(PowerPC::Read_U16_ZX, address);
+    ABI_CallFunctionPC(PowerPC::ReadU16ZXFromJit64, &m_jit.m_mmu, address);
     break;
   case 8:
-    ABI_CallFunctionC(PowerPC::Read_U8_ZX, address);
+    ABI_CallFunctionPC(PowerPC::ReadU8ZXFromJit64, &m_jit.m_mmu, address);
     break;
   }
   ABI_PopRegistersAndAdjustStack(registersInUse, 0);
@@ -578,16 +578,19 @@ void EmuCodeBlock::SafeWriteRegToReg(OpArg reg_value, X64Reg reg_addr, int acces
   switch (accessSize)
   {
   case 64:
-    ABI_CallFunctionRR(swap ? PowerPC::Write_U64 : PowerPC::Write_U64_Swap, reg, reg_addr);
+    ABI_CallFunctionPRR(swap ? PowerPC::WriteU64FromJit64 : PowerPC::WriteU64SwapFromJit64,
+                        &m_jit.m_mmu, reg, reg_addr);
     break;
   case 32:
-    ABI_CallFunctionRR(swap ? PowerPC::Write_U32 : PowerPC::Write_U32_Swap, reg, reg_addr);
+    ABI_CallFunctionPRR(swap ? PowerPC::WriteU32FromJit64 : PowerPC::WriteU32SwapFromJit64,
+                        &m_jit.m_mmu, reg, reg_addr);
     break;
   case 16:
-    ABI_CallFunctionRR(swap ? PowerPC::Write_U16 : PowerPC::Write_U16_Swap, reg, reg_addr);
+    ABI_CallFunctionPRR(swap ? PowerPC::WriteU16FromJit64 : PowerPC::WriteU16SwapFromJit64,
+                        &m_jit.m_mmu, reg, reg_addr);
     break;
   case 8:
-    ABI_CallFunctionRR(PowerPC::Write_U8, reg, reg_addr);
+    ABI_CallFunctionPRR(PowerPC::WriteU8FromJit64, &m_jit.m_mmu, reg, reg_addr);
     break;
   }
   ABI_PopRegistersAndAdjustStack(registersInUse, rsp_alignment);
@@ -623,7 +626,7 @@ bool EmuCodeBlock::WriteToConstAddress(int accessSize, OpArg arg, u32 address,
 
   // If we already know the address through constant folding, we can do some
   // fun tricks...
-  if (m_jit.jo.optimizeGatherPipe && PowerPC::IsOptimizableGatherPipeWrite(address))
+  if (m_jit.jo.optimizeGatherPipe && m_jit.m_mmu.IsOptimizableGatherPipeWrite(address))
   {
     X64Reg arg_reg = RSCRATCH;
 
@@ -643,7 +646,7 @@ bool EmuCodeBlock::WriteToConstAddress(int accessSize, OpArg arg, u32 address,
     m_jit.js.fifoBytesSinceCheck += accessSize >> 3;
     return false;
   }
-  else if (m_jit.jo.fastmem_arena && PowerPC::IsOptimizableRAMAddress(address))
+  else if (m_jit.jo.fastmem_arena && m_jit.m_mmu.IsOptimizableRAMAddress(address))
   {
     WriteToConstRamAddress(accessSize, arg, address);
     return false;
@@ -657,16 +660,16 @@ bool EmuCodeBlock::WriteToConstAddress(int accessSize, OpArg arg, u32 address,
     switch (accessSize)
     {
     case 64:
-      ABI_CallFunctionAC(64, PowerPC::Write_U64, arg, address);
+      ABI_CallFunctionPAC(64, PowerPC::WriteU64FromJit64, &m_jit.m_mmu, arg, address);
       break;
     case 32:
-      ABI_CallFunctionAC(32, PowerPC::Write_U32, arg, address);
+      ABI_CallFunctionPAC(32, PowerPC::WriteU32FromJit64, &m_jit.m_mmu, arg, address);
       break;
     case 16:
-      ABI_CallFunctionAC(16, PowerPC::Write_U16, arg, address);
+      ABI_CallFunctionPAC(16, PowerPC::WriteU16FromJit64, &m_jit.m_mmu, arg, address);
       break;
     case 8:
-      ABI_CallFunctionAC(8, PowerPC::Write_U8, arg, address);
+      ABI_CallFunctionPAC(8, PowerPC::WriteU8FromJit64, &m_jit.m_mmu, arg, address);
       break;
     }
     ABI_PopRegistersAndAdjustStack(registersInUse, 0);
diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp
index 7e0735f85d..ce01d560e2 100644
--- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp
+++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp
@@ -317,8 +317,8 @@ void JitArm64::IntializeSpeculativeConstants()
   for (auto i : code_block.m_gpr_inputs)
   {
     u32 compile_time_value = m_ppc_state.gpr[i];
-    if (PowerPC::IsOptimizableGatherPipeWrite(compile_time_value) ||
-        PowerPC::IsOptimizableGatherPipeWrite(compile_time_value - 0x8000) ||
+    if (m_mmu.IsOptimizableGatherPipeWrite(compile_time_value) ||
+        m_mmu.IsOptimizableGatherPipeWrite(compile_time_value - 0x8000) ||
         compile_time_value == 0xCC000000)
     {
       if (!fail)
diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_BackPatch.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64_BackPatch.cpp
index 3d34afab4d..8e14e694c4 100644
--- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_BackPatch.cpp
+++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_BackPatch.cpp
@@ -216,32 +216,48 @@ void JitArm64::EmitBackpatchRoutine(u32 flags, MemAccessMode mode, ARM64Reg RS,
 
       const bool reverse = (flags & BackPatchInfo::FLAG_REVERSE) != 0;
 
+      MOVP2R(ARM64Reg::X2, &m_mmu);
+
       if (access_size == 64)
-        MOVP2R(ARM64Reg::X8, reverse ? &PowerPC::Write_U64_Swap : &PowerPC::Write_U64);
+      {
+        MOVP2R(ARM64Reg::X8,
+               reverse ? &PowerPC::WriteU64SwapFromJitArm64 : &PowerPC::WriteU64FromJitArm64);
+      }
       else if (access_size == 32)
-        MOVP2R(ARM64Reg::X8, reverse ? &PowerPC::Write_U32_Swap : &PowerPC::Write_U32);
+      {
+        MOVP2R(ARM64Reg::X8,
+               reverse ? &PowerPC::WriteU32SwapFromJitArm64 : &PowerPC::WriteU32FromJitArm64);
+      }
       else if (access_size == 16)
-        MOVP2R(ARM64Reg::X8, reverse ? &PowerPC::Write_U16_Swap : &PowerPC::Write_U16);
+      {
+        MOVP2R(ARM64Reg::X8,
+               reverse ? &PowerPC::WriteU16SwapFromJitArm64 : &PowerPC::WriteU16FromJitArm64);
+      }
       else
-        MOVP2R(ARM64Reg::X8, &PowerPC::Write_U8);
+      {
+        MOVP2R(ARM64Reg::X8, &PowerPC::WriteU8FromJitArm64);
+      }
 
       BLR(ARM64Reg::X8);
     }
     else if (flags & BackPatchInfo::FLAG_ZERO_256)
     {
-      MOVP2R(ARM64Reg::X8, &PowerPC::ClearDCacheLine);
+      MOVP2R(ARM64Reg::X1, &m_mmu);
+      MOVP2R(ARM64Reg::X8, &PowerPC::ClearDCacheLineFromJitArm64);
       BLR(ARM64Reg::X8);
     }
     else
     {
+      MOVP2R(ARM64Reg::X1, &m_mmu);
+
       if (access_size == 64)
-        MOVP2R(ARM64Reg::X8, &PowerPC::Read_U64);
+        MOVP2R(ARM64Reg::X8, &PowerPC::ReadU64FromJitArm64);
       else if (access_size == 32)
-        MOVP2R(ARM64Reg::X8, &PowerPC::Read_U32);
+        MOVP2R(ARM64Reg::X8, &PowerPC::ReadU32FromJitArm64);
       else if (access_size == 16)
-        MOVP2R(ARM64Reg::X8, &PowerPC::Read_U16);
+        MOVP2R(ARM64Reg::X8, &PowerPC::ReadU16FromJitArm64);
       else
-        MOVP2R(ARM64Reg::X8, &PowerPC::Read_U8);
+        MOVP2R(ARM64Reg::X8, &PowerPC::ReadU8FromJitArm64);
 
       BLR(ARM64Reg::X8);
     }
diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStore.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStore.cpp
index d3f920b588..9bc573ddd5 100644
--- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStore.cpp
+++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStore.cpp
@@ -132,9 +132,9 @@ void JitArm64::SafeLoadToReg(u32 dest, s32 addr, s32 offsetReg, u32 flags, s32 o
   u32 access_size = BackPatchInfo::GetFlagSize(flags);
   u32 mmio_address = 0;
   if (is_immediate)
-    mmio_address = PowerPC::IsOptimizableMMIOAccess(imm_addr, access_size);
+    mmio_address = m_mmu.IsOptimizableMMIOAccess(imm_addr, access_size);
 
-  if (is_immediate && PowerPC::IsOptimizableRAMAddress(imm_addr))
+  if (is_immediate && m_mmu.IsOptimizableRAMAddress(imm_addr))
   {
     set_addr_reg_if_needed();
     EmitBackpatchRoutine(flags, MemAccessMode::AlwaysUnsafe, dest_reg, XA, regs_in_use,
@@ -278,9 +278,9 @@ void JitArm64::SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s
   u32 access_size = BackPatchInfo::GetFlagSize(flags);
   u32 mmio_address = 0;
   if (is_immediate)
-    mmio_address = PowerPC::IsOptimizableMMIOAccess(imm_addr, access_size);
+    mmio_address = m_mmu.IsOptimizableMMIOAccess(imm_addr, access_size);
 
-  if (is_immediate && jo.optimizeGatherPipe && PowerPC::IsOptimizableGatherPipeWrite(imm_addr))
+  if (is_immediate && jo.optimizeGatherPipe && m_mmu.IsOptimizableGatherPipeWrite(imm_addr))
   {
     int accessSize;
     if (flags & BackPatchInfo::FLAG_SIZE_32)
@@ -306,7 +306,7 @@ void JitArm64::SafeStoreFromReg(s32 dest, u32 value, s32 regOffset, u32 flags, s
 
     js.fifoBytesSinceCheck += accessSize >> 3;
   }
-  else if (is_immediate && PowerPC::IsOptimizableRAMAddress(imm_addr))
+  else if (is_immediate && m_mmu.IsOptimizableRAMAddress(imm_addr))
   {
     set_addr_reg_if_needed();
     EmitBackpatchRoutine(flags, MemAccessMode::AlwaysUnsafe, RS, XA, regs_in_use, fprs_in_use);
@@ -358,7 +358,7 @@ FixupBranch JitArm64::CheckIfSafeAddress(Arm64Gen::ARM64Reg addr, Arm64Gen::ARM6
 {
   tmp2 = EncodeRegTo64(tmp2);
 
-  MOVP2R(tmp2, PowerPC::dbat_table.data());
+  MOVP2R(tmp2, m_mmu.GetDBATTable().data());
   LSR(tmp1, addr, PowerPC::BAT_INDEX_SHIFT);
   LDR(tmp1, tmp2, ArithOption(tmp1, true));
   FixupBranch pass = TBNZ(tmp1, IntLog2(PowerPC::BAT_PHYSICAL_BIT));
@@ -730,7 +730,7 @@ void JitArm64::dcbx(UGeckoInstruction inst)
   if (m_ppc_state.msr.IR)
   {
     bat_lookup_failed =
-        BATAddressLookup(physical_addr, effective_addr, WA, PowerPC::ibat_table.data());
+        BATAddressLookup(physical_addr, effective_addr, WA, m_mmu.GetIBATTable().data());
     BFI(physical_addr, effective_addr, 0, PowerPC::BAT_INDEX_SHIFT);
   }
 
diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStoreFloating.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStoreFloating.cpp
index 281593d4a7..f83a6bf71d 100644
--- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStoreFloating.cpp
+++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_LoadStoreFloating.cpp
@@ -174,7 +174,7 @@ void JitArm64::lfXX(UGeckoInstruction inst)
   if (!jo.memcheck)
     fprs_in_use[DecodeReg(VD)] = 0;
 
-  if (is_immediate && PowerPC::IsOptimizableRAMAddress(imm_addr))
+  if (is_immediate && m_mmu.IsOptimizableRAMAddress(imm_addr))
   {
     EmitBackpatchRoutine(flags, MemAccessMode::AlwaysUnsafe, VD, XA, regs_in_use, fprs_in_use);
   }
@@ -378,7 +378,7 @@ void JitArm64::stfXX(UGeckoInstruction inst)
 
   if (is_immediate)
   {
-    if (jo.optimizeGatherPipe && PowerPC::IsOptimizableGatherPipeWrite(imm_addr))
+    if (jo.optimizeGatherPipe && m_mmu.IsOptimizableGatherPipeWrite(imm_addr))
     {
       int accessSize;
       if (flags & BackPatchInfo::FLAG_SIZE_64)
@@ -399,7 +399,7 @@ void JitArm64::stfXX(UGeckoInstruction inst)
       STR(IndexType::Unsigned, ARM64Reg::X0, PPC_REG, PPCSTATE_OFF(gather_pipe_ptr));
       js.fifoBytesSinceCheck += accessSize >> 3;
     }
-    else if (PowerPC::IsOptimizableRAMAddress(imm_addr))
+    else if (m_mmu.IsOptimizableRAMAddress(imm_addr))
     {
       set_addr_reg_if_needed();
       EmitBackpatchRoutine(flags, MemAccessMode::AlwaysUnsafe, V0, XA, regs_in_use, fprs_in_use);
diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp
index 7717751747..d0c32c743b 100644
--- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp
+++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp
@@ -61,7 +61,8 @@ void JitTrampoline(JitBase& jit, u32 em_address)
 }
 
 JitBase::JitBase(Core::System& system)
-    : m_code_buffer(code_buffer_size), m_system(system), m_ppc_state(system.GetPPCState())
+    : m_code_buffer(code_buffer_size), m_system(system), m_ppc_state(system.GetPPCState()),
+      m_mmu(system.GetMMU())
 {
   m_registered_config_callback_id = Config::AddConfigChangedCallback(
       [this] { Core::RunAsCPUThread([this] { RefreshConfig(); }); });
diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h
index 44a76dd451..fe9c7341b8 100644
--- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h
+++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h
@@ -198,6 +198,7 @@ public:
 
   Core::System& m_system;
   PowerPC::PowerPCState& m_ppc_state;
+  PowerPC::MMU& m_mmu;
 };
 
 void JitTrampoline(JitBase& jit, u32 em_address);
diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp
index 56e8d95534..2718dc3921 100644
--- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp
+++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp
@@ -92,7 +92,7 @@ void JitBaseBlockCache::RunOnBlocks(std::function<void(const JitBlock&)> f)
 
 JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address)
 {
-  const u32 physical_address = PowerPC::JitCache_TranslateAddress(em_address).address;
+  const u32 physical_address = m_jit.m_mmu.JitCache_TranslateAddress(em_address).address;
   JitBlock& b = block_map.emplace(physical_address, JitBlock())->second;
   b.effectiveAddress = em_address;
   b.physicalAddress = physical_address;
@@ -147,7 +147,7 @@ JitBlock* JitBaseBlockCache::GetBlockFromStartAddress(u32 addr, u32 msr)
   u32 translated_addr = addr;
   if (UReg_MSR(msr).IR)
   {
-    auto translated = PowerPC::JitCache_TranslateAddress(addr);
+    auto translated = m_jit.m_mmu.JitCache_TranslateAddress(addr);
     if (!translated.valid)
     {
       return nullptr;
@@ -186,7 +186,7 @@ const u8* JitBaseBlockCache::Dispatch()
 void JitBaseBlockCache::InvalidateICacheLine(u32 address)
 {
   const u32 cache_line_address = address & ~0x1f;
-  const auto translated = PowerPC::JitCache_TranslateAddress(cache_line_address);
+  const auto translated = m_jit.m_mmu.JitCache_TranslateAddress(cache_line_address);
   if (translated.valid)
     InvalidateICacheInternal(translated.address, cache_line_address, 32, false);
 }
@@ -197,7 +197,7 @@ void JitBaseBlockCache::InvalidateICache(u32 initial_address, u32 initial_length
   u32 length = initial_length;
   while (length > 0)
   {
-    const auto translated = PowerPC::JitCache_TranslateAddress(address);
+    const auto translated = m_jit.m_mmu.JitCache_TranslateAddress(address);
 
     const bool address_from_bat = translated.valid && translated.translated && translated.from_bat;
     const int shift = address_from_bat ? PowerPC::BAT_INDEX_SHIFT : PowerPC::HW_PAGE_INDEX_SHIFT;
diff --git a/Source/Core/Core/PowerPC/JitInterface.cpp b/Source/Core/Core/PowerPC/JitInterface.cpp
index beec9559e6..c2875ec82e 100644
--- a/Source/Core/Core/PowerPC/JitInterface.cpp
+++ b/Source/Core/Core/PowerPC/JitInterface.cpp
@@ -297,7 +297,7 @@ void JitInterface::CompileExceptionCheck(ExceptionType type)
 
       // Check in case the code has been replaced since: do we need to do this?
       const OpType optype =
-          PPCTables::GetOpInfo(PowerPC::HostRead_U32(guard, PowerPC::ppcState.pc))->type;
+          PPCTables::GetOpInfo(PowerPC::MMU::HostRead_U32(guard, PowerPC::ppcState.pc))->type;
       if (optype != OpType::Store && optype != OpType::StoreFP && optype != OpType::StorePS)
         return;
     }
diff --git a/Source/Core/Core/PowerPC/MMU.cpp b/Source/Core/Core/PowerPC/MMU.cpp
index 5a255c1c22..92786beec6 100644
--- a/Source/Core/Core/PowerPC/MMU.cpp
+++ b/Source/Core/Core/PowerPC/MMU.cpp
@@ -52,41 +52,39 @@
 
 namespace PowerPC
 {
+MMU::MMU(Core::System& system, Memory::MemoryManager& memory, PowerPC::PowerPCState& ppc_state)
+    : m_system(system), m_memory(memory), m_ppc_state(ppc_state)
+{
+}
+
+MMU::~MMU() = default;
+
 // Overloaded byteswap functions, for use within the templated functions below.
-inline u8 bswap(u8 val)
+[[maybe_unused]] static u8 bswap(u8 val)
 {
   return val;
 }
-inline s8 bswap(s8 val)
+[[maybe_unused]] static s8 bswap(s8 val)
 {
   return val;
 }
-inline u16 bswap(u16 val)
+[[maybe_unused]] static u16 bswap(u16 val)
 {
   return Common::swap16(val);
 }
-inline s16 bswap(s16 val)
+[[maybe_unused]] static s16 bswap(s16 val)
 {
   return Common::swap16(val);
 }
-inline u32 bswap(u32 val)
+[[maybe_unused]] static u32 bswap(u32 val)
 {
   return Common::swap32(val);
 }
-inline u64 bswap(u64 val)
+[[maybe_unused]] static u64 bswap(u64 val)
 {
   return Common::swap64(val);
 }
 
-enum class XCheckTLBFlag
-{
-  NoException,
-  Read,
-  Write,
-  Opcode,
-  OpcodeNoException
-};
-
 static bool IsOpcodeFlag(XCheckTLBFlag flag)
 {
   return flag == XCheckTLBFlag::Opcode || flag == XCheckTLBFlag::OpcodeNoException;
@@ -97,29 +95,6 @@ static bool IsNoExceptionFlag(XCheckTLBFlag flag)
   return flag == XCheckTLBFlag::NoException || flag == XCheckTLBFlag::OpcodeNoException;
 }
 
-enum class TranslateAddressResultEnum : u8
-{
-  BAT_TRANSLATED,
-  PAGE_TABLE_TRANSLATED,
-  DIRECT_STORE_SEGMENT,
-  PAGE_FAULT,
-};
-
-struct TranslateAddressResult
-{
-  u32 address;
-  TranslateAddressResultEnum result;
-  bool wi;  // Set to true if the view of memory is either write-through or cache-inhibited
-
-  TranslateAddressResult(TranslateAddressResultEnum result_, u32 address_, bool wi_ = false)
-      : address(address_), result(result_), wi(wi_)
-  {
-  }
-  bool Success() const { return result <= TranslateAddressResultEnum::PAGE_TABLE_TRANSLATED; }
-};
-template <const XCheckTLBFlag flag>
-static TranslateAddressResult TranslateAddress(u32 address);
-
 // Nasty but necessary. Super Mario Galaxy pointer relies on this stuff.
 static u32 EFB_Read(const u32 addr)
 {
@@ -170,13 +145,8 @@ static void EFB_Write(u32 data, u32 addr)
   }
 }
 
-BatTable ibat_table;
-BatTable dbat_table;
-
-static void GenerateDSIException(u32 effective_address, bool write);
-
-template <XCheckTLBFlag flag, typename T, bool never_translate = false>
-static T ReadFromHardware(Core::System& system, Memory::MemoryManager& memory, u32 em_address)
+template <XCheckTLBFlag flag, typename T, bool never_translate>
+T MMU::ReadFromHardware(u32 em_address)
 {
   const u32 em_address_start_page = em_address & ~HW_PAGE_MASK;
   const u32 em_address_end_page = (em_address + sizeof(T) - 1) & ~HW_PAGE_MASK;
@@ -189,15 +159,14 @@ static T ReadFromHardware(Core::System& system, Memory::MemoryManager& memory, u
     u64 var = 0;
     for (u32 i = 0; i < sizeof(T); ++i)
     {
-      var =
-          (var << 8) | ReadFromHardware<flag, u8, never_translate>(system, memory, em_address + i);
+      var = (var << 8) | ReadFromHardware<flag, u8, never_translate>(em_address + i);
     }
     return static_cast<T>(var);
   }
 
   bool wi = false;
 
-  if (!never_translate && PowerPC::ppcState.msr.DR)
+  if (!never_translate && m_ppc_state.msr.DR)
   {
     auto translated_addr = TranslateAddress<flag>(em_address);
     if (!translated_addr.Success())
@@ -215,52 +184,52 @@ static T ReadFromHardware(Core::System& system, Memory::MemoryManager& memory, u
     if (em_address < 0x0c000000)
       return EFB_Read(em_address);
     else
-      return static_cast<T>(memory.GetMMIOMapping()->Read<std::make_unsigned_t<T>>(em_address));
+      return static_cast<T>(m_memory.GetMMIOMapping()->Read<std::make_unsigned_t<T>>(em_address));
   }
 
   // Locked L1 technically doesn't have a fixed address, but games all use 0xE0000000.
-  if (memory.GetL1Cache() && (em_address >> 28) == 0xE &&
-      (em_address < (0xE0000000 + memory.GetL1CacheSize())))
+  if (m_memory.GetL1Cache() && (em_address >> 28) == 0xE &&
+      (em_address < (0xE0000000 + m_memory.GetL1CacheSize())))
   {
     T value;
-    std::memcpy(&value, &memory.GetL1Cache()[em_address & 0x0FFFFFFF], sizeof(T));
+    std::memcpy(&value, &m_memory.GetL1Cache()[em_address & 0x0FFFFFFF], sizeof(T));
     return bswap(value);
   }
 
-  if (memory.GetRAM() && (em_address & 0xF8000000) == 0x00000000)
+  if (m_memory.GetRAM() && (em_address & 0xF8000000) == 0x00000000)
   {
     // Handle RAM; the masking intentionally discards bits (essentially creating
     // mirrors of memory).
     T value;
-    em_address &= memory.GetRamMask();
+    em_address &= m_memory.GetRamMask();
 
-    if (!ppcState.m_enable_dcache || wi)
+    if (!m_ppc_state.m_enable_dcache || wi)
     {
-      std::memcpy(&value, &memory.GetRAM()[em_address], sizeof(T));
+      std::memcpy(&value, &m_memory.GetRAM()[em_address], sizeof(T));
     }
     else
     {
-      ppcState.dCache.Read(em_address, &value, sizeof(T),
-                           HID0(PowerPC::ppcState).DLOCK || flag != XCheckTLBFlag::Read);
+      m_ppc_state.dCache.Read(em_address, &value, sizeof(T),
+                              HID0(m_ppc_state).DLOCK || flag != XCheckTLBFlag::Read);
     }
 
     return bswap(value);
   }
 
-  if (memory.GetEXRAM() && (em_address >> 28) == 0x1 &&
-      (em_address & 0x0FFFFFFF) < memory.GetExRamSizeReal())
+  if (m_memory.GetEXRAM() && (em_address >> 28) == 0x1 &&
+      (em_address & 0x0FFFFFFF) < m_memory.GetExRamSizeReal())
   {
     T value;
     em_address &= 0x0FFFFFFF;
 
-    if (!ppcState.m_enable_dcache || wi)
+    if (!m_ppc_state.m_enable_dcache || wi)
     {
-      std::memcpy(&value, &memory.GetEXRAM()[em_address], sizeof(T));
+      std::memcpy(&value, &m_memory.GetEXRAM()[em_address], sizeof(T));
     }
     else
     {
-      ppcState.dCache.Read(em_address + 0x10000000, &value, sizeof(T),
-                           HID0(PowerPC::ppcState).DLOCK || flag != XCheckTLBFlag::Read);
+      m_ppc_state.dCache.Read(em_address + 0x10000000, &value, sizeof(T),
+                              HID0(m_ppc_state).DLOCK || flag != XCheckTLBFlag::Read);
     }
 
     return bswap(value);
@@ -269,25 +238,25 @@ static T ReadFromHardware(Core::System& system, Memory::MemoryManager& memory, u
   // In Fake-VMEM mode, we need to map the memory somewhere into
   // physical memory for BAT translation to work; we currently use
   // [0x7E000000, 0x80000000).
-  if (memory.GetFakeVMEM() && ((em_address & 0xFE000000) == 0x7E000000))
+  if (m_memory.GetFakeVMEM() && ((em_address & 0xFE000000) == 0x7E000000))
   {
     T value;
-    std::memcpy(&value, &memory.GetFakeVMEM()[em_address & memory.GetFakeVMemMask()], sizeof(T));
+    std::memcpy(&value, &m_memory.GetFakeVMEM()[em_address & m_memory.GetFakeVMemMask()],
+                sizeof(T));
     return bswap(value);
   }
 
-  PanicAlertFmt("Unable to resolve read address {:x} PC {:x}", em_address, PowerPC::ppcState.pc);
-  if (system.IsPauseOnPanicMode())
+  PanicAlertFmt("Unable to resolve read address {:x} PC {:x}", em_address, m_ppc_state.pc);
+  if (m_system.IsPauseOnPanicMode())
   {
-    system.GetCPU().Break();
-    ppcState.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
+    m_system.GetCPU().Break();
+    m_ppc_state.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
   }
   return 0;
 }
 
-template <XCheckTLBFlag flag, bool never_translate = false>
-static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory, u32 em_address,
-                            const u32 data, const u32 size)
+template <XCheckTLBFlag flag, bool never_translate>
+void MMU::WriteToHardware(u32 em_address, const u32 data, const u32 size)
 {
   DEBUG_ASSERT(size <= 4);
 
@@ -300,16 +269,15 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
     // Note that "word" means 32-bit, so paired singles or doubles might still be 32-bit aligned!
     const u32 first_half_size = em_address_end_page - em_address;
     const u32 second_half_size = size - first_half_size;
-    WriteToHardware<flag, never_translate>(system, memory, em_address,
-                                           std::rotr(data, second_half_size * 8), first_half_size);
-    WriteToHardware<flag, never_translate>(system, memory, em_address_end_page, data,
-                                           second_half_size);
+    WriteToHardware<flag, never_translate>(em_address, std::rotr(data, second_half_size * 8),
+                                           first_half_size);
+    WriteToHardware<flag, never_translate>(em_address_end_page, data, second_half_size);
     return;
   }
 
   bool wi = false;
 
-  if (!never_translate && PowerPC::ppcState.msr.DR)
+  if (!never_translate && m_ppc_state.msr.DR)
   {
     auto translated_addr = TranslateAddress<flag>(em_address);
     if (!translated_addr.Success())
@@ -338,17 +306,17 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
     switch (size)
     {
     case 1:
-      system.GetGPFifo().Write8(static_cast<u8>(data));
+      m_system.GetGPFifo().Write8(static_cast<u8>(data));
       return;
     case 2:
-      system.GetGPFifo().Write16(static_cast<u16>(data));
+      m_system.GetGPFifo().Write16(static_cast<u16>(data));
       return;
     case 4:
-      system.GetGPFifo().Write32(data);
+      m_system.GetGPFifo().Write32(data);
       return;
     default:
       // Some kind of misaligned write. TODO: Does this match how the actual hardware handles it?
-      auto& gpfifo = system.GetGPFifo();
+      auto& gpfifo = m_system.GetGPFifo();
       for (size_t i = size * 8; i > 0;)
       {
         i -= 8;
@@ -369,20 +337,20 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
     switch (size)
     {
     case 1:
-      memory.GetMMIOMapping()->Write<u8>(em_address, static_cast<u8>(data));
+      m_memory.GetMMIOMapping()->Write<u8>(em_address, static_cast<u8>(data));
       return;
     case 2:
-      memory.GetMMIOMapping()->Write<u16>(em_address, static_cast<u16>(data));
+      m_memory.GetMMIOMapping()->Write<u16>(em_address, static_cast<u16>(data));
       return;
     case 4:
-      memory.GetMMIOMapping()->Write<u32>(em_address, data);
+      m_memory.GetMMIOMapping()->Write<u32>(em_address, data);
       return;
     default:
       // Some kind of misaligned write. TODO: Does this match how the actual hardware handles it?
       for (size_t i = size * 8; i > 0; em_address++)
       {
         i -= 8;
-        memory.GetMMIOMapping()->Write<u8>(em_address, static_cast<u8>(data >> i));
+        m_memory.GetMMIOMapping()->Write<u8>(em_address, static_cast<u8>(data >> i));
       }
       return;
     }
@@ -391,10 +359,10 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
   const u32 swapped_data = Common::swap32(std::rotr(data, size * 8));
 
   // Locked L1 technically doesn't have a fixed address, but games all use 0xE0000000.
-  if (memory.GetL1Cache() && (em_address >> 28 == 0xE) &&
-      (em_address < (0xE0000000 + memory.GetL1CacheSize())))
+  if (m_memory.GetL1Cache() && (em_address >> 28 == 0xE) &&
+      (em_address < (0xE0000000 + m_memory.GetL1CacheSize())))
   {
-    std::memcpy(&memory.GetL1Cache()[em_address & 0x0FFFFFFF], &swapped_data, size);
+    std::memcpy(&m_memory.GetL1Cache()[em_address & 0x0FFFFFFF], &swapped_data, size);
     return;
   }
 
@@ -409,7 +377,7 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
     // TODO: This interrupt is supposed to have associated cause and address registers
     // TODO: This should trigger the hwtest's interrupt handling, but it does not seem to
     //       (https://github.com/dolphin-emu/hwtests/pull/42)
-    system.GetProcessorInterface().SetInterrupt(ProcessorInterface::INT_CAUSE_PI);
+    m_system.GetProcessorInterface().SetInterrupt(ProcessorInterface::INT_CAUSE_PI);
 
     const u32 rotated_data = std::rotr(data, ((em_address & 0x3) + size) * 8);
 
@@ -417,41 +385,41 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
     const u32 end_addr = Common::AlignUp(em_address + size, 8);
     for (u32 addr = start_addr; addr != end_addr; addr += 8)
     {
-      WriteToHardware<flag, true>(system, memory, addr, rotated_data, 4);
-      WriteToHardware<flag, true>(system, memory, addr + 4, rotated_data, 4);
+      WriteToHardware<flag, true>(addr, rotated_data, 4);
+      WriteToHardware<flag, true>(addr + 4, rotated_data, 4);
     }
 
     return;
   }
 
-  if (memory.GetRAM() && (em_address & 0xF8000000) == 0x00000000)
+  if (m_memory.GetRAM() && (em_address & 0xF8000000) == 0x00000000)
   {
     // Handle RAM; the masking intentionally discards bits (essentially creating
     // mirrors of memory).
-    em_address &= memory.GetRamMask();
+    em_address &= m_memory.GetRamMask();
 
-    if (ppcState.m_enable_dcache && !wi)
-      ppcState.dCache.Write(em_address, &swapped_data, size, HID0(PowerPC::ppcState).DLOCK);
+    if (m_ppc_state.m_enable_dcache && !wi)
+      m_ppc_state.dCache.Write(em_address, &swapped_data, size, HID0(m_ppc_state).DLOCK);
 
-    if (!ppcState.m_enable_dcache || wi || flag != XCheckTLBFlag::Write)
-      std::memcpy(&memory.GetRAM()[em_address], &swapped_data, size);
+    if (!m_ppc_state.m_enable_dcache || wi || flag != XCheckTLBFlag::Write)
+      std::memcpy(&m_memory.GetRAM()[em_address], &swapped_data, size);
 
     return;
   }
 
-  if (memory.GetEXRAM() && (em_address >> 28) == 0x1 &&
-      (em_address & 0x0FFFFFFF) < memory.GetExRamSizeReal())
+  if (m_memory.GetEXRAM() && (em_address >> 28) == 0x1 &&
+      (em_address & 0x0FFFFFFF) < m_memory.GetExRamSizeReal())
   {
     em_address &= 0x0FFFFFFF;
 
-    if (ppcState.m_enable_dcache && !wi)
+    if (m_ppc_state.m_enable_dcache && !wi)
     {
-      ppcState.dCache.Write(em_address + 0x10000000, &swapped_data, size,
-                            HID0(PowerPC::ppcState).DLOCK);
+      m_ppc_state.dCache.Write(em_address + 0x10000000, &swapped_data, size,
+                               HID0(m_ppc_state).DLOCK);
     }
 
-    if (!ppcState.m_enable_dcache || wi || flag != XCheckTLBFlag::Write)
-      std::memcpy(&memory.GetEXRAM()[em_address], &swapped_data, size);
+    if (!m_ppc_state.m_enable_dcache || wi || flag != XCheckTLBFlag::Write)
+      std::memcpy(&m_memory.GetEXRAM()[em_address], &swapped_data, size);
 
     return;
   }
@@ -459,17 +427,18 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
   // In Fake-VMEM mode, we need to map the memory somewhere into
   // physical memory for BAT translation to work; we currently use
   // [0x7E000000, 0x80000000).
-  if (memory.GetFakeVMEM() && ((em_address & 0xFE000000) == 0x7E000000))
+  if (m_memory.GetFakeVMEM() && ((em_address & 0xFE000000) == 0x7E000000))
   {
-    std::memcpy(&memory.GetFakeVMEM()[em_address & memory.GetFakeVMemMask()], &swapped_data, size);
+    std::memcpy(&m_memory.GetFakeVMEM()[em_address & m_memory.GetFakeVMemMask()], &swapped_data,
+                size);
     return;
   }
 
-  PanicAlertFmt("Unable to resolve write address {:x} PC {:x}", em_address, PowerPC::ppcState.pc);
-  if (system.IsPauseOnPanicMode())
+  PanicAlertFmt("Unable to resolve write address {:x} PC {:x}", em_address, m_ppc_state.pc);
+  if (m_system.IsPauseOnPanicMode())
   {
-    system.GetCPU().Break();
-    ppcState.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
+    m_system.GetCPU().Break();
+    m_ppc_state.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
   }
 }
 // =====================
@@ -479,9 +448,7 @@ static void WriteToHardware(Core::System& system, Memory::MemoryManager& memory,
    location through ReadFromHardware and WriteToHardware */
 // ----------------
 
-static void GenerateISIException(u32 effective_address);
-
-u32 Read_Opcode(u32 address)
+u32 MMU::Read_Opcode(u32 address)
 {
   TryReadInstResult result = TryReadInstruction(address);
   if (!result.valid)
@@ -492,10 +459,10 @@ u32 Read_Opcode(u32 address)
   return result.hex;
 }
 
-TryReadInstResult TryReadInstruction(u32 address)
+TryReadInstResult MMU::TryReadInstruction(u32 address)
 {
   bool from_bat = true;
-  if (PowerPC::ppcState.msr.IR)
+  if (m_ppc_state.msr.IR)
   {
     auto tlb_addr = TranslateAddress<XCheckTLBFlag::Opcode>(address);
     if (!tlb_addr.Success())
@@ -509,59 +476,50 @@ TryReadInstResult TryReadInstruction(u32 address)
     }
   }
 
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
   u32 hex;
   // TODO: Refactor this. This icache implementation is totally wrong if used with the fake vmem.
-  if (memory.GetFakeVMEM() && ((address & 0xFE000000) == 0x7E000000))
+  if (m_memory.GetFakeVMEM() && ((address & 0xFE000000) == 0x7E000000))
   {
-    hex = Common::swap32(&memory.GetFakeVMEM()[address & memory.GetFakeVMemMask()]);
+    hex = Common::swap32(&m_memory.GetFakeVMEM()[address & m_memory.GetFakeVMemMask()]);
   }
   else
   {
-    hex = PowerPC::ppcState.iCache.ReadInstruction(address);
+    hex = m_ppc_state.iCache.ReadInstruction(address);
   }
   return TryReadInstResult{true, from_bat, hex, address};
 }
 
-u32 HostRead_Instruction(const Core::CPUThreadGuard& guard, const u32 address)
+u32 MMU::HostRead_Instruction(const Core::CPUThreadGuard& guard, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  return ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32>(system, memory, address);
+  return guard.GetSystem().GetMMU().ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32>(
+      address);
 }
 
-std::optional<ReadResult<u32>> HostTryReadInstruction(const Core::CPUThreadGuard& guard,
-                                                      const u32 address,
-                                                      RequestedAddressSpace space)
+std::optional<ReadResult<u32>> MMU::HostTryReadInstruction(const Core::CPUThreadGuard& guard,
+                                                           const u32 address,
+                                                           RequestedAddressSpace space)
 {
   if (!HostIsInstructionRAMAddress(guard, address, space))
     return std::nullopt;
 
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-
+  auto& mmu = guard.GetSystem().GetMMU();
   switch (space)
   {
   case RequestedAddressSpace::Effective:
   {
-    const u32 value =
-        ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32>(system, memory, address);
-    return ReadResult<u32>(!!PowerPC::ppcState.msr.DR, value);
+    const u32 value = mmu.ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32>(address);
+    return ReadResult<u32>(!!mmu.m_ppc_state.msr.DR, value);
   }
   case RequestedAddressSpace::Physical:
   {
-    const u32 value =
-        ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32, true>(system, memory, address);
+    const u32 value = mmu.ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32, true>(address);
     return ReadResult<u32>(false, value);
   }
   case RequestedAddressSpace::Virtual:
   {
-    if (!PowerPC::ppcState.msr.DR)
+    if (!mmu.m_ppc_state.msr.DR)
       return std::nullopt;
-    const u32 value =
-        ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32>(system, memory, address);
+    const u32 value = mmu.ReadFromHardware<XCheckTLBFlag::OpcodeNoException, u32>(address);
     return ReadResult<u32>(true, value);
   }
   }
@@ -570,7 +528,7 @@ std::optional<ReadResult<u32>> HostTryReadInstruction(const Core::CPUThreadGuard
   return std::nullopt;
 }
 
-static void Memcheck(Core::System& system, u32 address, u64 var, bool write, size_t size)
+void MMU::Memcheck(u32 address, u64 var, bool write, size_t size)
 {
   if (!memchecks.HasAny())
     return;
@@ -579,7 +537,7 @@ static void Memcheck(Core::System& system, u32 address, u64 var, bool write, siz
   if (mc == nullptr)
     return;
 
-  if (system.GetCPU().IsStepping())
+  if (m_system.GetCPU().IsStepping())
   {
     // Disable when stepping so that resume works.
     return;
@@ -587,11 +545,11 @@ static void Memcheck(Core::System& system, u32 address, u64 var, bool write, siz
 
   mc->num_hits++;
 
-  const bool pause = mc->Action(&debug_interface, var, address, write, size, PowerPC::ppcState.pc);
+  const bool pause = mc->Action(&debug_interface, var, address, write, size, m_ppc_state.pc);
   if (!pause)
     return;
 
-  system.GetCPU().Break();
+  m_system.GetCPU().Break();
 
   if (GDBStub::IsActive())
     GDBStub::TakeControl();
@@ -603,86 +561,62 @@ static void Memcheck(Core::System& system, u32 address, u64 var, bool write, siz
   // make sure resuming after that works.)
   // It doesn't matter if ReadFromHardware triggers its own DSI because
   // we'll take it after resuming.
-  ppcState.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
+  m_ppc_state.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
 }
 
-u8 Read_U8(const u32 address)
+u8 MMU::Read_U8(const u32 address)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  u8 var = ReadFromHardware<XCheckTLBFlag::Read, u8>(system, memory, address);
-  Memcheck(system, address, var, false, 1);
+  u8 var = ReadFromHardware<XCheckTLBFlag::Read, u8>(address);
+  Memcheck(address, var, false, 1);
   return var;
 }
 
-u16 Read_U16(const u32 address)
+u16 MMU::Read_U16(const u32 address)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  u16 var = ReadFromHardware<XCheckTLBFlag::Read, u16>(system, memory, address);
-  Memcheck(system, address, var, false, 2);
+  u16 var = ReadFromHardware<XCheckTLBFlag::Read, u16>(address);
+  Memcheck(address, var, false, 2);
   return var;
 }
 
-u32 Read_U32(const u32 address)
+u32 MMU::Read_U32(const u32 address)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  u32 var = ReadFromHardware<XCheckTLBFlag::Read, u32>(system, memory, address);
-  Memcheck(system, address, var, false, 4);
+  u32 var = ReadFromHardware<XCheckTLBFlag::Read, u32>(address);
+  Memcheck(address, var, false, 4);
   return var;
 }
 
-u64 Read_U64(const u32 address)
+u64 MMU::Read_U64(const u32 address)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  u64 var = ReadFromHardware<XCheckTLBFlag::Read, u64>(system, memory, address);
-  Memcheck(system, address, var, false, 8);
+  u64 var = ReadFromHardware<XCheckTLBFlag::Read, u64>(address);
+  Memcheck(address, var, false, 8);
   return var;
 }
 
-double Read_F64(const u32 address)
-{
-  const u64 integral = Read_U64(address);
-
-  return Common::BitCast<double>(integral);
-}
-
-float Read_F32(const u32 address)
-{
-  const u32 integral = Read_U32(address);
-
-  return Common::BitCast<float>(integral);
-}
-
 template <typename T>
-static std::optional<ReadResult<T>> HostTryReadUX(const Core::CPUThreadGuard& guard,
-                                                  const u32 address, RequestedAddressSpace space)
+std::optional<ReadResult<T>> MMU::HostTryReadUX(const Core::CPUThreadGuard& guard,
+                                                const u32 address, RequestedAddressSpace space)
 {
   if (!HostIsRAMAddress(guard, address, space))
     return std::nullopt;
 
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-
+  auto& mmu = guard.GetSystem().GetMMU();
   switch (space)
   {
   case RequestedAddressSpace::Effective:
   {
-    T value = ReadFromHardware<XCheckTLBFlag::NoException, T>(system, memory, address);
-    return ReadResult<T>(!!PowerPC::ppcState.msr.DR, std::move(value));
+    T value = mmu.ReadFromHardware<XCheckTLBFlag::NoException, T>(address);
+    return ReadResult<T>(!!mmu.m_ppc_state.msr.DR, std::move(value));
   }
   case RequestedAddressSpace::Physical:
   {
-    T value = ReadFromHardware<XCheckTLBFlag::NoException, T, true>(system, memory, address);
+    T value = mmu.ReadFromHardware<XCheckTLBFlag::NoException, T, true>(address);
     return ReadResult<T>(false, std::move(value));
   }
   case RequestedAddressSpace::Virtual:
   {
-    if (!PowerPC::ppcState.msr.DR)
+    if (!mmu.m_ppc_state.msr.DR)
       return std::nullopt;
-    T value = ReadFromHardware<XCheckTLBFlag::NoException, T>(system, memory, address);
+    T value = mmu.ReadFromHardware<XCheckTLBFlag::NoException, T>(address);
     return ReadResult<T>(true, std::move(value));
   }
   }
@@ -691,32 +625,32 @@ static std::optional<ReadResult<T>> HostTryReadUX(const Core::CPUThreadGuard& gu
   return std::nullopt;
 }
 
-std::optional<ReadResult<u8>> HostTryReadU8(const Core::CPUThreadGuard& guard, u32 address,
-                                            RequestedAddressSpace space)
+std::optional<ReadResult<u8>> MMU::HostTryReadU8(const Core::CPUThreadGuard& guard, u32 address,
+                                                 RequestedAddressSpace space)
 {
   return HostTryReadUX<u8>(guard, address, space);
 }
 
-std::optional<ReadResult<u16>> HostTryReadU16(const Core::CPUThreadGuard& guard, u32 address,
-                                              RequestedAddressSpace space)
+std::optional<ReadResult<u16>> MMU::HostTryReadU16(const Core::CPUThreadGuard& guard, u32 address,
+                                                   RequestedAddressSpace space)
 {
   return HostTryReadUX<u16>(guard, address, space);
 }
 
-std::optional<ReadResult<u32>> HostTryReadU32(const Core::CPUThreadGuard& guard, u32 address,
-                                              RequestedAddressSpace space)
+std::optional<ReadResult<u32>> MMU::HostTryReadU32(const Core::CPUThreadGuard& guard, u32 address,
+                                                   RequestedAddressSpace space)
 {
   return HostTryReadUX<u32>(guard, address, space);
 }
 
-std::optional<ReadResult<u64>> HostTryReadU64(const Core::CPUThreadGuard& guard, u32 address,
-                                              RequestedAddressSpace space)
+std::optional<ReadResult<u64>> MMU::HostTryReadU64(const Core::CPUThreadGuard& guard, u32 address,
+                                                   RequestedAddressSpace space)
 {
   return HostTryReadUX<u64>(guard, address, space);
 }
 
-std::optional<ReadResult<float>> HostTryReadF32(const Core::CPUThreadGuard& guard, u32 address,
-                                                RequestedAddressSpace space)
+std::optional<ReadResult<float>> MMU::HostTryReadF32(const Core::CPUThreadGuard& guard, u32 address,
+                                                     RequestedAddressSpace space)
 {
   const auto result = HostTryReadUX<u32>(guard, address, space);
   if (!result)
@@ -724,8 +658,8 @@ std::optional<ReadResult<float>> HostTryReadF32(const Core::CPUThreadGuard& guar
   return ReadResult<float>(result->translated, Common::BitCast<float>(result->value));
 }
 
-std::optional<ReadResult<double>> HostTryReadF64(const Core::CPUThreadGuard& guard, u32 address,
-                                                 RequestedAddressSpace space)
+std::optional<ReadResult<double>> MMU::HostTryReadF64(const Core::CPUThreadGuard& guard,
+                                                      u32 address, RequestedAddressSpace space)
 {
   const auto result = HostTryReadUX<u64>(guard, address, space);
   if (!result)
@@ -733,178 +667,140 @@ std::optional<ReadResult<double>> HostTryReadF64(const Core::CPUThreadGuard& gua
   return ReadResult<double>(result->translated, Common::BitCast<double>(result->value));
 }
 
-u32 Read_U8_ZX(const u32 address)
+void MMU::Write_U8(const u32 var, const u32 address)
 {
-  return Read_U8(address);
+  Memcheck(address, var, true, 1);
+  WriteToHardware<XCheckTLBFlag::Write>(address, var, 1);
 }
 
-u32 Read_U16_ZX(const u32 address)
+void MMU::Write_U16(const u32 var, const u32 address)
 {
-  return Read_U16(address);
+  Memcheck(address, var, true, 2);
+  WriteToHardware<XCheckTLBFlag::Write>(address, var, 2);
 }
-
-void Write_U8(const u32 var, const u32 address)
-{
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  Memcheck(system, address, var, true, 1);
-  WriteToHardware<XCheckTLBFlag::Write>(system, memory, address, var, 1);
-}
-
-void Write_U16(const u32 var, const u32 address)
-{
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  Memcheck(system, address, var, true, 2);
-  WriteToHardware<XCheckTLBFlag::Write>(system, memory, address, var, 2);
-}
-void Write_U16_Swap(const u32 var, const u32 address)
+void MMU::Write_U16_Swap(const u32 var, const u32 address)
 {
   Write_U16((var & 0xFFFF0000) | Common::swap16(static_cast<u16>(var)), address);
 }
 
-void Write_U32(const u32 var, const u32 address)
+void MMU::Write_U32(const u32 var, const u32 address)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  Memcheck(system, address, var, true, 4);
-  WriteToHardware<XCheckTLBFlag::Write>(system, memory, address, var, 4);
+  Memcheck(address, var, true, 4);
+  WriteToHardware<XCheckTLBFlag::Write>(address, var, 4);
 }
-void Write_U32_Swap(const u32 var, const u32 address)
+void MMU::Write_U32_Swap(const u32 var, const u32 address)
 {
   Write_U32(Common::swap32(var), address);
 }
 
-void Write_U64(const u64 var, const u32 address)
+void MMU::Write_U64(const u64 var, const u32 address)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-  Memcheck(system, address, var, true, 8);
-  WriteToHardware<XCheckTLBFlag::Write>(system, memory, address, static_cast<u32>(var >> 32), 4);
-  WriteToHardware<XCheckTLBFlag::Write>(system, memory, address + sizeof(u32),
-                                        static_cast<u32>(var), 4);
+  Memcheck(address, var, true, 8);
+  WriteToHardware<XCheckTLBFlag::Write>(address, static_cast<u32>(var >> 32), 4);
+  WriteToHardware<XCheckTLBFlag::Write>(address + sizeof(u32), static_cast<u32>(var), 4);
 }
-void Write_U64_Swap(const u64 var, const u32 address)
+void MMU::Write_U64_Swap(const u64 var, const u32 address)
 {
   Write_U64(Common::swap64(var), address);
 }
 
-void Write_F64(const double var, const u32 address)
+u8 MMU::HostRead_U8(const Core::CPUThreadGuard& guard, const u32 address)
 {
-  const u64 integral = Common::BitCast<u64>(var);
-
-  Write_U64(integral, address);
+  auto& mmu = guard.GetSystem().GetMMU();
+  return mmu.ReadFromHardware<XCheckTLBFlag::NoException, u8>(address);
 }
 
-u8 HostRead_U8(const Core::CPUThreadGuard& guard, const u32 address)
+u16 MMU::HostRead_U16(const Core::CPUThreadGuard& guard, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  return ReadFromHardware<XCheckTLBFlag::NoException, u8>(system, memory, address);
+  auto& mmu = guard.GetSystem().GetMMU();
+  return mmu.ReadFromHardware<XCheckTLBFlag::NoException, u16>(address);
 }
 
-u16 HostRead_U16(const Core::CPUThreadGuard& guard, const u32 address)
+u32 MMU::HostRead_U32(const Core::CPUThreadGuard& guard, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  return ReadFromHardware<XCheckTLBFlag::NoException, u16>(system, memory, address);
+  auto& mmu = guard.GetSystem().GetMMU();
+  return mmu.ReadFromHardware<XCheckTLBFlag::NoException, u32>(address);
 }
 
-u32 HostRead_U32(const Core::CPUThreadGuard& guard, const u32 address)
+u64 MMU::HostRead_U64(const Core::CPUThreadGuard& guard, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  return ReadFromHardware<XCheckTLBFlag::NoException, u32>(system, memory, address);
+  auto& mmu = guard.GetSystem().GetMMU();
+  return mmu.ReadFromHardware<XCheckTLBFlag::NoException, u64>(address);
 }
 
-u64 HostRead_U64(const Core::CPUThreadGuard& guard, const u32 address)
-{
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  return ReadFromHardware<XCheckTLBFlag::NoException, u64>(system, memory, address);
-}
-
-float HostRead_F32(const Core::CPUThreadGuard& guard, const u32 address)
+float MMU::HostRead_F32(const Core::CPUThreadGuard& guard, const u32 address)
 {
   const u32 integral = HostRead_U32(guard, address);
 
   return Common::BitCast<float>(integral);
 }
 
-double HostRead_F64(const Core::CPUThreadGuard& guard, const u32 address)
+double MMU::HostRead_F64(const Core::CPUThreadGuard& guard, const u32 address)
 {
   const u64 integral = HostRead_U64(guard, address);
 
   return Common::BitCast<double>(integral);
 }
 
-void HostWrite_U8(const Core::CPUThreadGuard& guard, const u32 var, const u32 address)
+void MMU::HostWrite_U8(const Core::CPUThreadGuard& guard, const u32 var, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  WriteToHardware<XCheckTLBFlag::NoException>(system, memory, address, var, 1);
+  auto& mmu = guard.GetSystem().GetMMU();
+  mmu.WriteToHardware<XCheckTLBFlag::NoException>(address, var, 1);
 }
 
-void HostWrite_U16(const Core::CPUThreadGuard& guard, const u32 var, const u32 address)
+void MMU::HostWrite_U16(const Core::CPUThreadGuard& guard, const u32 var, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  WriteToHardware<XCheckTLBFlag::NoException>(system, memory, address, var, 2);
+  auto& mmu = guard.GetSystem().GetMMU();
+  mmu.WriteToHardware<XCheckTLBFlag::NoException>(address, var, 2);
 }
 
-void HostWrite_U32(const Core::CPUThreadGuard& guard, const u32 var, const u32 address)
+void MMU::HostWrite_U32(const Core::CPUThreadGuard& guard, const u32 var, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  WriteToHardware<XCheckTLBFlag::NoException>(system, memory, address, var, 4);
+  auto& mmu = guard.GetSystem().GetMMU();
+  mmu.WriteToHardware<XCheckTLBFlag::NoException>(address, var, 4);
 }
 
-void HostWrite_U64(const Core::CPUThreadGuard& guard, const u64 var, const u32 address)
+void MMU::HostWrite_U64(const Core::CPUThreadGuard& guard, const u64 var, const u32 address)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-  WriteToHardware<XCheckTLBFlag::NoException>(system, memory, address, static_cast<u32>(var >> 32),
-                                              4);
-  WriteToHardware<XCheckTLBFlag::NoException>(system, memory, address + sizeof(u32),
-                                              static_cast<u32>(var), 4);
+  auto& mmu = guard.GetSystem().GetMMU();
+  mmu.WriteToHardware<XCheckTLBFlag::NoException>(address, static_cast<u32>(var >> 32), 4);
+  mmu.WriteToHardware<XCheckTLBFlag::NoException>(address + sizeof(u32), static_cast<u32>(var), 4);
 }
 
-void HostWrite_F32(const Core::CPUThreadGuard& guard, const float var, const u32 address)
+void MMU::HostWrite_F32(const Core::CPUThreadGuard& guard, const float var, const u32 address)
 {
   const u32 integral = Common::BitCast<u32>(var);
 
   HostWrite_U32(guard, integral, address);
 }
 
-void HostWrite_F64(const Core::CPUThreadGuard& guard, const double var, const u32 address)
+void MMU::HostWrite_F64(const Core::CPUThreadGuard& guard, const double var, const u32 address)
 {
   const u64 integral = Common::BitCast<u64>(var);
 
   HostWrite_U64(guard, integral, address);
 }
 
-static std::optional<WriteResult> HostTryWriteUX(const Core::CPUThreadGuard& guard, const u32 var,
-                                                 const u32 address, const u32 size,
-                                                 RequestedAddressSpace space)
+std::optional<WriteResult> MMU::HostTryWriteUX(const Core::CPUThreadGuard& guard, const u32 var,
+                                               const u32 address, const u32 size,
+                                               RequestedAddressSpace space)
 {
   if (!HostIsRAMAddress(guard, address, space))
     return std::nullopt;
 
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-
+  auto& mmu = guard.GetSystem().GetMMU();
   switch (space)
   {
   case RequestedAddressSpace::Effective:
-    WriteToHardware<XCheckTLBFlag::NoException>(system, memory, address, var, size);
-    return WriteResult(!!PowerPC::ppcState.msr.DR);
+    mmu.WriteToHardware<XCheckTLBFlag::NoException>(address, var, size);
+    return WriteResult(!!mmu.m_ppc_state.msr.DR);
   case RequestedAddressSpace::Physical:
-    WriteToHardware<XCheckTLBFlag::NoException, true>(system, memory, address, var, size);
+    mmu.WriteToHardware<XCheckTLBFlag::NoException, true>(address, var, size);
     return WriteResult(false);
   case RequestedAddressSpace::Virtual:
-    if (!PowerPC::ppcState.msr.DR)
+    if (!mmu.m_ppc_state.msr.DR)
       return std::nullopt;
-    WriteToHardware<XCheckTLBFlag::NoException>(system, memory, address, var, size);
+    mmu.WriteToHardware<XCheckTLBFlag::NoException>(address, var, size);
     return WriteResult(true);
   }
 
@@ -912,26 +808,26 @@ static std::optional<WriteResult> HostTryWriteUX(const Core::CPUThreadGuard& gua
   return std::nullopt;
 }
 
-std::optional<WriteResult> HostTryWriteU8(const Core::CPUThreadGuard& guard, const u32 var,
-                                          const u32 address, RequestedAddressSpace space)
+std::optional<WriteResult> MMU::HostTryWriteU8(const Core::CPUThreadGuard& guard, const u32 var,
+                                               const u32 address, RequestedAddressSpace space)
 {
   return HostTryWriteUX(guard, var, address, 1, space);
 }
 
-std::optional<WriteResult> HostTryWriteU16(const Core::CPUThreadGuard& guard, const u32 var,
-                                           const u32 address, RequestedAddressSpace space)
+std::optional<WriteResult> MMU::HostTryWriteU16(const Core::CPUThreadGuard& guard, const u32 var,
+                                                const u32 address, RequestedAddressSpace space)
 {
   return HostTryWriteUX(guard, var, address, 2, space);
 }
 
-std::optional<WriteResult> HostTryWriteU32(const Core::CPUThreadGuard& guard, const u32 var,
-                                           const u32 address, RequestedAddressSpace space)
+std::optional<WriteResult> MMU::HostTryWriteU32(const Core::CPUThreadGuard& guard, const u32 var,
+                                                const u32 address, RequestedAddressSpace space)
 {
   return HostTryWriteUX(guard, var, address, 4, space);
 }
 
-std::optional<WriteResult> HostTryWriteU64(const Core::CPUThreadGuard& guard, const u64 var,
-                                           const u32 address, RequestedAddressSpace space)
+std::optional<WriteResult> MMU::HostTryWriteU64(const Core::CPUThreadGuard& guard, const u64 var,
+                                                const u32 address, RequestedAddressSpace space)
 {
   const auto result = HostTryWriteUX(guard, static_cast<u32>(var >> 32), address, 4, space);
   if (!result)
@@ -940,21 +836,21 @@ std::optional<WriteResult> HostTryWriteU64(const Core::CPUThreadGuard& guard, co
   return HostTryWriteUX(guard, static_cast<u32>(var), address + 4, 4, space);
 }
 
-std::optional<WriteResult> HostTryWriteF32(const Core::CPUThreadGuard& guard, const float var,
-                                           const u32 address, RequestedAddressSpace space)
+std::optional<WriteResult> MMU::HostTryWriteF32(const Core::CPUThreadGuard& guard, const float var,
+                                                const u32 address, RequestedAddressSpace space)
 {
   const u32 integral = Common::BitCast<u32>(var);
   return HostTryWriteU32(guard, integral, address, space);
 }
 
-std::optional<WriteResult> HostTryWriteF64(const Core::CPUThreadGuard& guard, const double var,
-                                           const u32 address, RequestedAddressSpace space)
+std::optional<WriteResult> MMU::HostTryWriteF64(const Core::CPUThreadGuard& guard, const double var,
+                                                const u32 address, RequestedAddressSpace space)
 {
   const u64 integral = Common::BitCast<u64>(var);
   return HostTryWriteU64(guard, integral, address, space);
 }
 
-std::string HostGetString(const Core::CPUThreadGuard& guard, u32 address, size_t size)
+std::string MMU::HostGetString(const Core::CPUThreadGuard& guard, u32 address, size_t size)
 {
   std::string s;
   do
@@ -970,9 +866,9 @@ std::string HostGetString(const Core::CPUThreadGuard& guard, u32 address, size_t
   return s;
 }
 
-std::optional<ReadResult<std::string>> HostTryReadString(const Core::CPUThreadGuard& guard,
-                                                         u32 address, size_t size,
-                                                         RequestedAddressSpace space)
+std::optional<ReadResult<std::string>> MMU::HostTryReadString(const Core::CPUThreadGuard& guard,
+                                                              u32 address, size_t size,
+                                                              RequestedAddressSpace space)
 {
   auto c = HostTryReadU8(guard, address, space);
   if (!c)
@@ -993,24 +889,24 @@ std::optional<ReadResult<std::string>> HostTryReadString(const Core::CPUThreadGu
   return ReadResult<std::string>(c->translated, std::move(s));
 }
 
-bool IsOptimizableRAMAddress(const u32 address)
+bool MMU::IsOptimizableRAMAddress(const u32 address) const
 {
   if (PowerPC::memchecks.HasAny())
     return false;
 
-  if (!PowerPC::ppcState.msr.DR)
+  if (!m_ppc_state.msr.DR)
     return false;
 
   // TODO: This API needs to take an access size
   //
   // We store whether an access can be optimized to an unchecked access
   // in dbat_table.
-  u32 bat_result = dbat_table[address >> BAT_INDEX_SHIFT];
+  u32 bat_result = m_dbat_table[address >> BAT_INDEX_SHIFT];
   return (bat_result & BAT_PHYSICAL_BIT) != 0;
 }
 
 template <XCheckTLBFlag flag>
-static bool IsRAMAddress(Memory::MemoryManager& memory, u32 address, bool translate)
+bool MMU::IsRAMAddress(u32 address, bool translate)
 {
   if (translate)
   {
@@ -1021,80 +917,73 @@ static bool IsRAMAddress(Memory::MemoryManager& memory, u32 address, bool transl
   }
 
   u32 segment = address >> 28;
-  if (memory.GetRAM() && segment == 0x0 && (address & 0x0FFFFFFF) < memory.GetRamSizeReal())
+  if (m_memory.GetRAM() && segment == 0x0 && (address & 0x0FFFFFFF) < m_memory.GetRamSizeReal())
   {
     return true;
   }
-  else if (memory.GetEXRAM() && segment == 0x1 &&
-           (address & 0x0FFFFFFF) < memory.GetExRamSizeReal())
+  else if (m_memory.GetEXRAM() && segment == 0x1 &&
+           (address & 0x0FFFFFFF) < m_memory.GetExRamSizeReal())
   {
     return true;
   }
-  else if (memory.GetFakeVMEM() && ((address & 0xFE000000) == 0x7E000000))
+  else if (m_memory.GetFakeVMEM() && ((address & 0xFE000000) == 0x7E000000))
   {
     return true;
   }
-  else if (memory.GetL1Cache() && segment == 0xE &&
-           (address < (0xE0000000 + memory.GetL1CacheSize())))
+  else if (m_memory.GetL1Cache() && segment == 0xE &&
+           (address < (0xE0000000 + m_memory.GetL1CacheSize())))
   {
     return true;
   }
   return false;
 }
 
-bool HostIsRAMAddress(const Core::CPUThreadGuard& guard, u32 address, RequestedAddressSpace space)
+bool MMU::HostIsRAMAddress(const Core::CPUThreadGuard& guard, u32 address,
+                           RequestedAddressSpace space)
 {
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-
+  auto& mmu = guard.GetSystem().GetMMU();
   switch (space)
   {
   case RequestedAddressSpace::Effective:
-    return IsRAMAddress<XCheckTLBFlag::NoException>(memory, address, PowerPC::ppcState.msr.DR);
+    return mmu.IsRAMAddress<XCheckTLBFlag::NoException>(address, mmu.m_ppc_state.msr.DR);
   case RequestedAddressSpace::Physical:
-    return IsRAMAddress<XCheckTLBFlag::NoException>(memory, address, false);
+    return mmu.IsRAMAddress<XCheckTLBFlag::NoException>(address, false);
   case RequestedAddressSpace::Virtual:
-    if (!PowerPC::ppcState.msr.DR)
+    if (!mmu.m_ppc_state.msr.DR)
       return false;
-    return IsRAMAddress<XCheckTLBFlag::NoException>(memory, address, true);
+    return mmu.IsRAMAddress<XCheckTLBFlag::NoException>(address, true);
   }
 
   ASSERT(false);
   return false;
 }
 
-bool HostIsInstructionRAMAddress(const Core::CPUThreadGuard& guard, u32 address,
-                                 RequestedAddressSpace space)
+bool MMU::HostIsInstructionRAMAddress(const Core::CPUThreadGuard& guard, u32 address,
+                                      RequestedAddressSpace space)
 {
   // Instructions are always 32bit aligned.
   if (address & 3)
     return false;
 
-  auto& system = guard.GetSystem();
-  auto& memory = system.GetMemory();
-
+  auto& mmu = guard.GetSystem().GetMMU();
   switch (space)
   {
   case RequestedAddressSpace::Effective:
-    return IsRAMAddress<XCheckTLBFlag::OpcodeNoException>(memory, address,
-                                                          PowerPC::ppcState.msr.IR);
+    return mmu.IsRAMAddress<XCheckTLBFlag::OpcodeNoException>(address, mmu.m_ppc_state.msr.IR);
   case RequestedAddressSpace::Physical:
-    return IsRAMAddress<XCheckTLBFlag::OpcodeNoException>(memory, address, false);
+    return mmu.IsRAMAddress<XCheckTLBFlag::OpcodeNoException>(address, false);
   case RequestedAddressSpace::Virtual:
-    if (!PowerPC::ppcState.msr.IR)
+    if (!mmu.m_ppc_state.msr.IR)
       return false;
-    return IsRAMAddress<XCheckTLBFlag::OpcodeNoException>(memory, address, true);
+    return mmu.IsRAMAddress<XCheckTLBFlag::OpcodeNoException>(address, true);
   }
 
   ASSERT(false);
   return false;
 }
 
-void DMA_LCToMemory(const u32 mem_address, const u32 cache_address, const u32 num_blocks)
+void MMU::DMA_LCToMemory(const u32 mem_address, const u32 cache_address, const u32 num_blocks)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
   // TODO: It's not completely clear this is the right spot for this code;
   // what would happen if, for example, the DVD drive tried to write to the EFB?
   // TODO: This is terribly slow.
@@ -1104,7 +993,7 @@ void DMA_LCToMemory(const u32 mem_address, const u32 cache_address, const u32 nu
   {
     for (u32 i = 0; i < 32 * num_blocks; i += 4)
     {
-      const u32 data = Common::swap32(memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF));
+      const u32 data = Common::swap32(m_memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF));
       EFB_Write(data, mem_address + i);
     }
     return;
@@ -1116,27 +1005,24 @@ void DMA_LCToMemory(const u32 mem_address, const u32 cache_address, const u32 nu
   {
     for (u32 i = 0; i < 32 * num_blocks; i += 4)
     {
-      const u32 data = Common::swap32(memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF));
-      memory.GetMMIOMapping()->Write(mem_address + i, data);
+      const u32 data = Common::swap32(m_memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF));
+      m_memory.GetMMIOMapping()->Write(mem_address + i, data);
     }
     return;
   }
 
-  const u8* src = memory.GetL1Cache() + (cache_address & 0x3FFFF);
-  u8* dst = memory.GetPointer(mem_address);
+  const u8* src = m_memory.GetL1Cache() + (cache_address & 0x3FFFF);
+  u8* dst = m_memory.GetPointer(mem_address);
   if (dst == nullptr)
     return;
 
   memcpy(dst, src, 32 * num_blocks);
 }
 
-void DMA_MemoryToLC(const u32 cache_address, const u32 mem_address, const u32 num_blocks)
+void MMU::DMA_MemoryToLC(const u32 cache_address, const u32 mem_address, const u32 num_blocks)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
-  const u8* src = memory.GetPointer(mem_address);
-  u8* dst = memory.GetL1Cache() + (cache_address & 0x3FFFF);
+  const u8* src = m_memory.GetPointer(mem_address);
+  u8* dst = m_memory.GetL1Cache() + (cache_address & 0x3FFFF);
 
   // No known game uses this; here for completeness.
   // TODO: Refactor.
@@ -1145,7 +1031,7 @@ void DMA_MemoryToLC(const u32 cache_address, const u32 mem_address, const u32 nu
     for (u32 i = 0; i < 32 * num_blocks; i += 4)
     {
       const u32 data = Common::swap32(EFB_Read(mem_address + i));
-      std::memcpy(memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF), &data, sizeof(u32));
+      std::memcpy(m_memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF), &data, sizeof(u32));
     }
     return;
   }
@@ -1156,8 +1042,8 @@ void DMA_MemoryToLC(const u32 cache_address, const u32 mem_address, const u32 nu
   {
     for (u32 i = 0; i < 32 * num_blocks; i += 4)
     {
-      const u32 data = Common::swap32(memory.GetMMIOMapping()->Read<u32>(mem_address + i));
-      std::memcpy(memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF), &data, sizeof(u32));
+      const u32 data = Common::swap32(m_memory.GetMMIOMapping()->Read<u32>(mem_address + i));
+      std::memcpy(m_memory.GetL1Cache() + ((cache_address + i) & 0x3FFFF), &data, sizeof(u32));
     }
     return;
   }
@@ -1168,10 +1054,20 @@ void DMA_MemoryToLC(const u32 cache_address, const u32 mem_address, const u32 nu
   memcpy(dst, src, 32 * num_blocks);
 }
 
-void ClearDCacheLine(u32 address)
+static bool TranslateBatAddress(const BatTable& bat_table, u32* address, bool* wi)
+{
+  u32 bat_result = bat_table[*address >> BAT_INDEX_SHIFT];
+  if ((bat_result & BAT_MAPPED_BIT) == 0)
+    return false;
+  *address = (bat_result & BAT_RESULT_MASK) | (*address & (BAT_PAGE_SIZE - 1));
+  *wi = (bat_result & BAT_WI_BIT) != 0;
+  return true;
+}
+
+void MMU::ClearDCacheLine(u32 address)
 {
   DEBUG_ASSERT((address & 0x1F) == 0);
-  if (PowerPC::ppcState.msr.DR)
+  if (m_ppc_state.msr.DR)
   {
     auto translated_address = TranslateAddress<XCheckTLBFlag::Write>(address);
     if (translated_address.result == TranslateAddressResultEnum::DIRECT_STORE_SEGMENT)
@@ -1190,20 +1086,17 @@ void ClearDCacheLine(u32 address)
     address = translated_address.address;
   }
 
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
   // TODO: This isn't precisely correct for non-RAM regions, but the difference
   // is unlikely to matter.
   for (u32 i = 0; i < 32; i += 4)
-    WriteToHardware<XCheckTLBFlag::Write, true>(system, memory, address + i, 0, 4);
+    WriteToHardware<XCheckTLBFlag::Write, true>(address + i, 0, 4);
 }
 
-void StoreDCacheLine(u32 address)
+void MMU::StoreDCacheLine(u32 address)
 {
   address &= ~0x1F;
 
-  if (PowerPC::ppcState.msr.DR)
+  if (m_ppc_state.msr.DR)
   {
     auto translated_address = TranslateAddress<XCheckTLBFlag::Write>(address);
     if (translated_address.result == TranslateAddressResultEnum::DIRECT_STORE_SEGMENT)
@@ -1219,15 +1112,15 @@ void StoreDCacheLine(u32 address)
     address = translated_address.address;
   }
 
-  if (ppcState.m_enable_dcache)
-    ppcState.dCache.Store(address);
+  if (m_ppc_state.m_enable_dcache)
+    m_ppc_state.dCache.Store(address);
 }
 
-void InvalidateDCacheLine(u32 address)
+void MMU::InvalidateDCacheLine(u32 address)
 {
   address &= ~0x1F;
 
-  if (PowerPC::ppcState.msr.DR)
+  if (m_ppc_state.msr.DR)
   {
     auto translated_address = TranslateAddress<XCheckTLBFlag::Write>(address);
     if (translated_address.result == TranslateAddressResultEnum::DIRECT_STORE_SEGMENT)
@@ -1241,15 +1134,15 @@ void InvalidateDCacheLine(u32 address)
     address = translated_address.address;
   }
 
-  if (ppcState.m_enable_dcache)
-    ppcState.dCache.Invalidate(address);
+  if (m_ppc_state.m_enable_dcache)
+    m_ppc_state.dCache.Invalidate(address);
 }
 
-void FlushDCacheLine(u32 address)
+void MMU::FlushDCacheLine(u32 address)
 {
   address &= ~0x1F;
 
-  if (PowerPC::ppcState.msr.DR)
+  if (m_ppc_state.msr.DR)
   {
     auto translated_address = TranslateAddress<XCheckTLBFlag::Write>(address);
     if (translated_address.result == TranslateAddressResultEnum::DIRECT_STORE_SEGMENT)
@@ -1265,15 +1158,15 @@ void FlushDCacheLine(u32 address)
     address = translated_address.address;
   }
 
-  if (ppcState.m_enable_dcache)
-    ppcState.dCache.Flush(address);
+  if (m_ppc_state.m_enable_dcache)
+    m_ppc_state.dCache.Flush(address);
 }
 
-void TouchDCacheLine(u32 address, bool store)
+void MMU::TouchDCacheLine(u32 address, bool store)
 {
   address &= ~0x1F;
 
-  if (PowerPC::ppcState.msr.DR)
+  if (m_ppc_state.msr.DR)
   {
     auto translated_address = TranslateAddress<XCheckTLBFlag::Write>(address);
     if (translated_address.result == TranslateAddressResultEnum::DIRECT_STORE_SEGMENT)
@@ -1289,23 +1182,23 @@ void TouchDCacheLine(u32 address, bool store)
     address = translated_address.address;
   }
 
-  if (ppcState.m_enable_dcache)
-    ppcState.dCache.Touch(address, store);
+  if (m_ppc_state.m_enable_dcache)
+    m_ppc_state.dCache.Touch(address, store);
 }
 
-u32 IsOptimizableMMIOAccess(u32 address, u32 access_size)
+u32 MMU::IsOptimizableMMIOAccess(u32 address, u32 access_size) const
 {
   if (PowerPC::memchecks.HasAny())
     return 0;
 
-  if (!PowerPC::ppcState.msr.DR)
+  if (!m_ppc_state.msr.DR)
     return 0;
 
   // Translate address
   // If we also optimize for TLB mappings, we'd have to clear the
   // JitCache on each TLB invalidation.
   bool wi = false;
-  if (!TranslateBatAddess(dbat_table, &address, &wi))
+  if (!TranslateBatAddress(m_dbat_table, &address, &wi))
     return 0;
 
   // Check whether the address is an aligned address of an MMIO register.
@@ -1316,28 +1209,28 @@ u32 IsOptimizableMMIOAccess(u32 address, u32 access_size)
   return address;
 }
 
-bool IsOptimizableGatherPipeWrite(u32 address)
+bool MMU::IsOptimizableGatherPipeWrite(u32 address) const
 {
   if (PowerPC::memchecks.HasAny())
     return false;
 
-  if (!PowerPC::ppcState.msr.DR)
+  if (!m_ppc_state.msr.DR)
     return false;
 
   // Translate address, only check BAT mapping.
   // If we also optimize for TLB mappings, we'd have to clear the
   // JitCache on each TLB invalidation.
   bool wi = false;
-  if (!TranslateBatAddess(dbat_table, &address, &wi))
+  if (!TranslateBatAddress(m_dbat_table, &address, &wi))
     return false;
 
   // Check whether the translated address equals the address in WPAR.
   return address == GPFifo::GATHER_PIPE_PHYSICAL_ADDRESS;
 }
 
-TranslateResult JitCache_TranslateAddress(u32 address)
+TranslateResult MMU::JitCache_TranslateAddress(u32 address)
 {
-  if (!PowerPC::ppcState.msr.IR)
+  if (!m_ppc_state.msr.IR)
     return TranslateResult{address};
 
   // TODO: We shouldn't use FLAG_OPCODE if the caller is the debugger.
@@ -1349,18 +1242,17 @@ TranslateResult JitCache_TranslateAddress(u32 address)
   return TranslateResult{from_bat, tlb_addr.address};
 }
 
-static void GenerateDSIException(u32 effective_address, bool write)
+void MMU::GenerateDSIException(u32 effective_address, bool write)
 {
   // DSI exceptions are only supported in MMU mode.
-  auto& system = Core::System::GetInstance();
-  if (!system.IsMMUMode())
+  if (!m_system.IsMMUMode())
   {
     PanicAlertFmt("Invalid {} {:#010x}, PC = {:#010x}", write ? "write to" : "read from",
-                  effective_address, PowerPC::ppcState.pc);
-    if (system.IsPauseOnPanicMode())
+                  effective_address, m_ppc_state.pc);
+    if (m_system.IsPauseOnPanicMode())
     {
-      system.GetCPU().Break();
-      ppcState.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
+      m_system.GetCPU().Break();
+      m_ppc_state.Exceptions |= EXCEPTION_DSI | EXCEPTION_FAKE_MEMCHECK_HIT;
     }
     return;
   }
@@ -1369,27 +1261,27 @@ static void GenerateDSIException(u32 effective_address, bool write)
   constexpr u32 dsisr_store = 1U << 25;
 
   if (effective_address != 0)
-    ppcState.spr[SPR_DSISR] = dsisr_page | dsisr_store;
+    m_ppc_state.spr[SPR_DSISR] = dsisr_page | dsisr_store;
   else
-    ppcState.spr[SPR_DSISR] = dsisr_page;
+    m_ppc_state.spr[SPR_DSISR] = dsisr_page;
 
-  ppcState.spr[SPR_DAR] = effective_address;
+  m_ppc_state.spr[SPR_DAR] = effective_address;
 
-  ppcState.Exceptions |= EXCEPTION_DSI;
+  m_ppc_state.Exceptions |= EXCEPTION_DSI;
 }
 
-static void GenerateISIException(u32 effective_address)
+void MMU::GenerateISIException(u32 effective_address)
 {
   // Address of instruction could not be translated
-  PowerPC::ppcState.npc = effective_address;
+  m_ppc_state.npc = effective_address;
 
-  PowerPC::ppcState.Exceptions |= EXCEPTION_ISI;
-  WARN_LOG_FMT(POWERPC, "ISI exception at {:#010x}", PowerPC::ppcState.pc);
+  m_ppc_state.Exceptions |= EXCEPTION_ISI;
+  WARN_LOG_FMT(POWERPC, "ISI exception at {:#010x}", m_ppc_state.pc);
 }
 
-void SDRUpdated()
+void MMU::SDRUpdated()
 {
-  const auto sdr = UReg_SDR1{ppcState.spr[SPR_SDR]};
+  const auto sdr = UReg_SDR1{m_ppc_state.spr[SPR_SDR]};
   const u32 htabmask = sdr.htabmask;
 
   if (!Common::IsValidLowMask(htabmask))
@@ -1403,8 +1295,8 @@ void SDRUpdated()
   if ((htaborg & htabmask) != 0)
     WARN_LOG_FMT(POWERPC, "Invalid HTABORG: htaborg=0x{:08x} htabmask=0x{:08x}", htaborg, htabmask);
 
-  ppcState.pagetable_base = htaborg << 16;
-  ppcState.pagetable_hashmask = ((htabmask << 10) | 0x3ff);
+  m_ppc_state.pagetable_base = htaborg << 16;
+  m_ppc_state.pagetable_hashmask = ((htabmask << 10) | 0x3ff);
 }
 
 enum class TLBLookupResult
@@ -1414,11 +1306,12 @@ enum class TLBLookupResult
   UpdateC
 };
 
-static TLBLookupResult LookupTLBPageAddress(const XCheckTLBFlag flag, const u32 vpa, u32* paddr,
+static TLBLookupResult LookupTLBPageAddress(PowerPC::PowerPCState& ppc_state,
+                                            const XCheckTLBFlag flag, const u32 vpa, u32* paddr,
                                             bool* wi)
 {
   const u32 tag = vpa >> HW_PAGE_INDEX_SHIFT;
-  TLBEntry& tlbe = ppcState.tlb[IsOpcodeFlag(flag)][tag & HW_PAGE_INDEX_MASK];
+  TLBEntry& tlbe = ppc_state.tlb[IsOpcodeFlag(flag)][tag & HW_PAGE_INDEX_MASK];
 
   if (tlbe.tag[0] == tag)
   {
@@ -1469,13 +1362,14 @@ static TLBLookupResult LookupTLBPageAddress(const XCheckTLBFlag flag, const u32
   return TLBLookupResult::NotFound;
 }
 
-static void UpdateTLBEntry(const XCheckTLBFlag flag, UPTE_Hi pte2, const u32 address)
+static void UpdateTLBEntry(PowerPC::PowerPCState& ppc_state, const XCheckTLBFlag flag, UPTE_Hi pte2,
+                           const u32 address)
 {
   if (IsNoExceptionFlag(flag))
     return;
 
   const u32 tag = address >> HW_PAGE_INDEX_SHIFT;
-  TLBEntry& tlbe = ppcState.tlb[IsOpcodeFlag(flag)][tag & HW_PAGE_INDEX_MASK];
+  TLBEntry& tlbe = ppc_state.tlb[IsOpcodeFlag(flag)][tag & HW_PAGE_INDEX_MASK];
   const u32 index = tlbe.recent == 0 && tlbe.tag[0] != TLBEntry::INVALID_TAG;
   tlbe.recent = index;
   tlbe.paddr[index] = pte2.RPN << HW_PAGE_INDEX_SHIFT;
@@ -1483,43 +1377,31 @@ static void UpdateTLBEntry(const XCheckTLBFlag flag, UPTE_Hi pte2, const u32 add
   tlbe.tag[index] = tag;
 }
 
-void InvalidateTLBEntry(u32 address)
+void MMU::InvalidateTLBEntry(u32 address)
 {
   const u32 entry_index = (address >> HW_PAGE_INDEX_SHIFT) & HW_PAGE_INDEX_MASK;
 
-  ppcState.tlb[0][entry_index].Invalidate();
-  ppcState.tlb[1][entry_index].Invalidate();
+  m_ppc_state.tlb[0][entry_index].Invalidate();
+  m_ppc_state.tlb[1][entry_index].Invalidate();
 }
 
-union EffectiveAddress
-{
-  BitField<0, 12, u32> offset;
-  BitField<12, 16, u32> page_index;
-  BitField<22, 6, u32> API;
-  BitField<28, 4, u32> SR;
-
-  u32 Hex = 0;
-
-  EffectiveAddress() = default;
-  explicit EffectiveAddress(u32 address) : Hex{address} {}
-};
-
 // Page Address Translation
-static TranslateAddressResult TranslatePageAddress(const EffectiveAddress address,
-                                                   const XCheckTLBFlag flag, bool* wi)
+MMU::TranslateAddressResult MMU::TranslatePageAddress(const EffectiveAddress address,
+                                                      const XCheckTLBFlag flag, bool* wi)
 {
   // TLB cache
   // This catches 99%+ of lookups in practice, so the actual page table entry code below doesn't
   // benefit much from optimization.
   u32 translated_address = 0;
-  const TLBLookupResult res = LookupTLBPageAddress(flag, address.Hex, &translated_address, wi);
+  const TLBLookupResult res =
+      LookupTLBPageAddress(m_ppc_state, flag, address.Hex, &translated_address, wi);
   if (res == TLBLookupResult::Found)
   {
     return TranslateAddressResult{TranslateAddressResultEnum::PAGE_TABLE_TRANSLATED,
                                   translated_address};
   }
 
-  const auto sr = UReg_SR{ppcState.sr[address.SR]};
+  const auto sr = UReg_SR{m_ppc_state.sr[address.SR]};
 
   if (sr.T != 0)
     return TranslateAddressResult{TranslateAddressResultEnum::DIRECT_STORE_SEGMENT, 0};
@@ -1545,9 +1427,6 @@ static TranslateAddressResult TranslatePageAddress(const EffectiveAddress addres
   pte1.API = api;
   pte1.V = 1;
 
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
   for (int hash_func = 0; hash_func < 2; hash_func++)
   {
     // hash function no 2 "not" .360
@@ -1557,16 +1436,15 @@ static TranslateAddressResult TranslatePageAddress(const EffectiveAddress addres
       pte1.H = 1;
     }
 
-    u32 pteg_addr =
-        ((hash & PowerPC::ppcState.pagetable_hashmask) << 6) | PowerPC::ppcState.pagetable_base;
+    u32 pteg_addr = ((hash & m_ppc_state.pagetable_hashmask) << 6) | m_ppc_state.pagetable_base;
 
     for (int i = 0; i < 8; i++, pteg_addr += 8)
     {
-      const u32 pteg = memory.Read_U32(pteg_addr);
+      const u32 pteg = m_memory.Read_U32(pteg_addr);
 
       if (pte1.Hex == pteg)
       {
-        UPTE_Hi pte2(memory.Read_U32(pteg_addr + 4));
+        UPTE_Hi pte2(m_memory.Read_U32(pteg_addr + 4));
 
         // set the access bits
         switch (flag)
@@ -1588,12 +1466,12 @@ static TranslateAddressResult TranslatePageAddress(const EffectiveAddress addres
 
         if (!IsNoExceptionFlag(flag))
         {
-          memory.Write_U32(pte2.Hex, pteg_addr + 4);
+          m_memory.Write_U32(pte2.Hex, pteg_addr + 4);
         }
 
         // We already updated the TLB entry if this was caused by a C bit.
         if (res != TLBLookupResult::UpdateC)
-          UpdateTLBEntry(flag, pte2, address.Hex);
+          UpdateTLBEntry(m_ppc_state, flag, pte2, address.Hex);
 
         *wi = (pte2.WIMG & 0b1100) != 0;
 
@@ -1605,11 +1483,8 @@ static TranslateAddressResult TranslatePageAddress(const EffectiveAddress addres
   return TranslateAddressResult{TranslateAddressResultEnum::PAGE_FAULT, 0};
 }
 
-static void UpdateBATs(BatTable& bat_table, u32 base_spr)
+void MMU::UpdateBATs(BatTable& bat_table, u32 base_spr)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
   // TODO: Separate BATs for MSR.PR==0 and MSR.PR==1
   // TODO: Handle PP settings.
   // TODO: Check how hardware reacts to overlapping BATs (including
@@ -1618,8 +1493,8 @@ static void UpdateBATs(BatTable& bat_table, u32 base_spr)
   for (int i = 0; i < 4; ++i)
   {
     const u32 spr = base_spr + i * 2;
-    const UReg_BAT_Up batu{ppcState.spr[spr]};
-    const UReg_BAT_Lo batl{ppcState.spr[spr + 1]};
+    const UReg_BAT_Up batu{m_ppc_state.spr[spr]};
+    const UReg_BAT_Lo batl{m_ppc_state.spr[spr + 1]};
     if (batu.VS == 0 && batu.VP == 0)
       continue;
 
@@ -1669,21 +1544,21 @@ static void UpdateBATs(BatTable& bat_table, u32 base_spr)
         // that fastmem doesn't emulate properly (though no normal games are known to rely on them).
         if (!wi)
         {
-          if (memory.GetFakeVMEM() && (physical_address & 0xFE000000) == 0x7E000000)
+          if (m_memory.GetFakeVMEM() && (physical_address & 0xFE000000) == 0x7E000000)
           {
             valid_bit |= BAT_PHYSICAL_BIT;
           }
-          else if (physical_address < memory.GetRamSizeReal())
+          else if (physical_address < m_memory.GetRamSizeReal())
           {
             valid_bit |= BAT_PHYSICAL_BIT;
           }
-          else if (memory.GetEXRAM() && physical_address >> 28 == 0x1 &&
-                   (physical_address & 0x0FFFFFFF) < memory.GetExRamSizeReal())
+          else if (m_memory.GetEXRAM() && physical_address >> 28 == 0x1 &&
+                   (physical_address & 0x0FFFFFFF) < m_memory.GetExRamSizeReal())
           {
             valid_bit |= BAT_PHYSICAL_BIT;
           }
           else if (physical_address >> 28 == 0xE &&
-                   physical_address < 0xE0000000 + memory.GetL1CacheSize())
+                   physical_address < 0xE0000000 + m_memory.GetL1CacheSize())
           {
             valid_bit |= BAT_PHYSICAL_BIT;
           }
@@ -1700,17 +1575,14 @@ static void UpdateBATs(BatTable& bat_table, u32 base_spr)
   }
 }
 
-static void UpdateFakeMMUBat(BatTable& bat_table, u32 start_addr)
+void MMU::UpdateFakeMMUBat(BatTable& bat_table, u32 start_addr)
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
   for (u32 i = 0; i < (0x10000000 >> BAT_INDEX_SHIFT); ++i)
   {
     // Map from 0x4XXXXXXX or 0x7XXXXXXX to the range
     // [0x7E000000,0x80000000).
     u32 e_address = i + (start_addr >> BAT_INDEX_SHIFT);
-    u32 p_address = 0x7E000000 | (i << BAT_INDEX_SHIFT & memory.GetFakeVMemMask());
+    u32 p_address = 0x7E000000 | (i << BAT_INDEX_SHIFT & m_memory.GetFakeVMemMask());
     u32 flags = BAT_MAPPED_BIT | BAT_PHYSICAL_BIT;
 
     if (PowerPC::memchecks.OverlapsMemcheck(e_address << BAT_INDEX_SHIFT, BAT_PAGE_SIZE))
@@ -1720,48 +1592,42 @@ static void UpdateFakeMMUBat(BatTable& bat_table, u32 start_addr)
   }
 }
 
-void DBATUpdated()
+void MMU::DBATUpdated()
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
-  dbat_table = {};
-  UpdateBATs(dbat_table, SPR_DBAT0U);
-  bool extended_bats = SConfig::GetInstance().bWii && HID4(PowerPC::ppcState).SBE;
+  m_dbat_table = {};
+  UpdateBATs(m_dbat_table, SPR_DBAT0U);
+  bool extended_bats = SConfig::GetInstance().bWii && HID4(m_ppc_state).SBE;
   if (extended_bats)
-    UpdateBATs(dbat_table, SPR_DBAT4U);
-  if (memory.GetFakeVMEM())
+    UpdateBATs(m_dbat_table, SPR_DBAT4U);
+  if (m_memory.GetFakeVMEM())
   {
     // In Fake-MMU mode, insert some extra entries into the BAT tables.
-    UpdateFakeMMUBat(dbat_table, 0x40000000);
-    UpdateFakeMMUBat(dbat_table, 0x70000000);
+    UpdateFakeMMUBat(m_dbat_table, 0x40000000);
+    UpdateFakeMMUBat(m_dbat_table, 0x70000000);
   }
 
 #ifndef _ARCH_32
-  memory.UpdateLogicalMemory(dbat_table);
+  m_memory.UpdateLogicalMemory(m_dbat_table);
 #endif
 
   // IsOptimizable*Address and dcbz depends on the BAT mapping, so we need a flush here.
-  system.GetJitInterface().ClearSafe();
+  m_system.GetJitInterface().ClearSafe();
 }
 
-void IBATUpdated()
+void MMU::IBATUpdated()
 {
-  auto& system = Core::System::GetInstance();
-  auto& memory = system.GetMemory();
-
-  ibat_table = {};
-  UpdateBATs(ibat_table, SPR_IBAT0U);
-  bool extended_bats = SConfig::GetInstance().bWii && HID4(PowerPC::ppcState).SBE;
+  m_ibat_table = {};
+  UpdateBATs(m_ibat_table, SPR_IBAT0U);
+  bool extended_bats = SConfig::GetInstance().bWii && HID4(m_ppc_state).SBE;
   if (extended_bats)
-    UpdateBATs(ibat_table, SPR_IBAT4U);
-  if (memory.GetFakeVMEM())
+    UpdateBATs(m_ibat_table, SPR_IBAT4U);
+  if (m_memory.GetFakeVMEM())
   {
     // In Fake-MMU mode, insert some extra entries into the BAT tables.
-    UpdateFakeMMUBat(ibat_table, 0x40000000);
-    UpdateFakeMMUBat(ibat_table, 0x70000000);
+    UpdateFakeMMUBat(m_ibat_table, 0x40000000);
+    UpdateFakeMMUBat(m_ibat_table, 0x70000000);
   }
-  system.GetJitInterface().ClearSafe();
+  m_system.GetJitInterface().ClearSafe();
 }
 
 // Translate effective address using BAT or PAT.  Returns 0 if the address cannot be translated.
@@ -1769,17 +1635,17 @@ void IBATUpdated()
 // So we first check if there is a matching BAT entry, else we look for the TLB in
 // TranslatePageAddress().
 template <const XCheckTLBFlag flag>
-static TranslateAddressResult TranslateAddress(u32 address)
+MMU::TranslateAddressResult MMU::TranslateAddress(u32 address)
 {
   bool wi = false;
 
-  if (TranslateBatAddess(IsOpcodeFlag(flag) ? ibat_table : dbat_table, &address, &wi))
+  if (TranslateBatAddress(IsOpcodeFlag(flag) ? m_ibat_table : m_dbat_table, &address, &wi))
     return TranslateAddressResult{TranslateAddressResultEnum::BAT_TRANSLATED, address, wi};
 
   return TranslatePageAddress(EffectiveAddress{address}, flag, &wi);
 }
 
-std::optional<u32> GetTranslatedAddress(u32 address)
+std::optional<u32> MMU::GetTranslatedAddress(u32 address)
 {
   auto result = TranslateAddress<XCheckTLBFlag::NoException>(address);
   if (!result.Success())
@@ -1789,4 +1655,101 @@ std::optional<u32> GetTranslatedAddress(u32 address)
   return std::optional<u32>(result.address);
 }
 
+void ClearDCacheLineFromJit64(MMU& mmu, u32 address)
+{
+  mmu.ClearDCacheLine(address);
+}
+u32 ReadU8ZXFromJit64(MMU& mmu, u32 address)
+{
+  return mmu.Read_U8(address);
+}
+u32 ReadU16ZXFromJit64(MMU& mmu, u32 address)
+{
+  return mmu.Read_U16(address);
+}
+u32 ReadU32FromJit64(MMU& mmu, u32 address)
+{
+  return mmu.Read_U32(address);
+}
+u64 ReadU64FromJit64(MMU& mmu, u32 address)
+{
+  return mmu.Read_U64(address);
+}
+void WriteU8FromJit64(MMU& mmu, u32 var, u32 address)
+{
+  mmu.Write_U8(var, address);
+}
+void WriteU16FromJit64(MMU& mmu, u32 var, u32 address)
+{
+  mmu.Write_U16(var, address);
+}
+void WriteU32FromJit64(MMU& mmu, u32 var, u32 address)
+{
+  mmu.Write_U32(var, address);
+}
+void WriteU64FromJit64(MMU& mmu, u64 var, u32 address)
+{
+  mmu.Write_U64(var, address);
+}
+void WriteU16SwapFromJit64(MMU& mmu, u32 var, u32 address)
+{
+  mmu.Write_U16_Swap(var, address);
+}
+void WriteU32SwapFromJit64(MMU& mmu, u32 var, u32 address)
+{
+  mmu.Write_U32_Swap(var, address);
+}
+void WriteU64SwapFromJit64(MMU& mmu, u64 var, u32 address)
+{
+  mmu.Write_U64_Swap(var, address);
+}
+
+void ClearDCacheLineFromJitArm64(u32 address, MMU& mmu)
+{
+  mmu.ClearDCacheLine(address);
+}
+u8 ReadU8FromJitArm64(u32 address, MMU& mmu)
+{
+  return mmu.Read_U8(address);
+}
+u16 ReadU16FromJitArm64(u32 address, MMU& mmu)
+{
+  return mmu.Read_U16(address);
+}
+u32 ReadU32FromJitArm64(u32 address, MMU& mmu)
+{
+  return mmu.Read_U32(address);
+}
+u64 ReadU64FromJitArm64(u32 address, MMU& mmu)
+{
+  return mmu.Read_U64(address);
+}
+void WriteU8FromJitArm64(u32 var, u32 address, MMU& mmu)
+{
+  mmu.Write_U8(var, address);
+}
+void WriteU16FromJitArm64(u32 var, u32 address, MMU& mmu)
+{
+  mmu.Write_U16(var, address);
+}
+void WriteU32FromJitArm64(u32 var, u32 address, MMU& mmu)
+{
+  mmu.Write_U32(var, address);
+}
+void WriteU64FromJitArm64(u64 var, u32 address, MMU& mmu)
+{
+  mmu.Write_U64(var, address);
+}
+void WriteU16SwapFromJitArm64(u32 var, u32 address, MMU& mmu)
+{
+  mmu.Write_U16_Swap(var, address);
+}
+void WriteU32SwapFromJitArm64(u32 var, u32 address, MMU& mmu)
+{
+  mmu.Write_U32_Swap(var, address);
+}
+void WriteU64SwapFromJitArm64(u64 var, u32 address, MMU& mmu)
+{
+  mmu.Write_U64_Swap(var, address);
+}
 }  // namespace PowerPC
diff --git a/Source/Core/Core/PowerPC/MMU.h b/Source/Core/Core/PowerPC/MMU.h
index bf9ee0d926..a5dda345b8 100644
--- a/Source/Core/Core/PowerPC/MMU.h
+++ b/Source/Core/Core/PowerPC/MMU.h
@@ -8,18 +8,22 @@
 #include <optional>
 #include <string>
 
+#include "Common/BitField.h"
 #include "Common/CommonTypes.h"
 
 namespace Core
 {
 class CPUThreadGuard;
+class System;
+};  // namespace Core
+namespace Memory
+{
+class MemoryManager;
 };
 
 namespace PowerPC
 {
-// Routines for debugger UI, cheats, etc. to access emulated memory from the
-// perspective of the CPU.  Not for use by core emulation routines.
-// Use "Host" prefix.
+struct PowerPCState;
 
 enum class RequestedAddressSpace
 {
@@ -28,19 +32,6 @@ enum class RequestedAddressSpace
   Virtual,    // specifically want MMU turned on, fails if off
 };
 
-// Reads a value from emulated memory using the currently active MMU settings.
-// If the read fails (eg. address does not correspond to a mapped address in the current address
-// space), a PanicAlert will be shown to the user and zero (or an empty string for the string case)
-// will be returned.
-u8 HostRead_U8(const Core::CPUThreadGuard& guard, u32 address);
-u16 HostRead_U16(const Core::CPUThreadGuard& guard, u32 address);
-u32 HostRead_U32(const Core::CPUThreadGuard& guard, u32 address);
-u64 HostRead_U64(const Core::CPUThreadGuard& guard, u32 address);
-float HostRead_F32(const Core::CPUThreadGuard& guard, u32 address);
-double HostRead_F64(const Core::CPUThreadGuard& guard, u32 address);
-u32 HostRead_Instruction(const Core::CPUThreadGuard& guard, u32 address);
-std::string HostGetString(const Core::CPUThreadGuard& guard, u32 address, size_t size = 0);
-
 template <typename T>
 struct ReadResult
 {
@@ -57,45 +48,6 @@ struct ReadResult
   ReadResult(bool translated_, const T& value_) : translated(translated_), value(value_) {}
 };
 
-// Try to read a value from emulated memory at the given address in the given memory space.
-// If the read succeeds, the returned value will be present and the ReadResult contains the read
-// value and information on whether the given address had to be translated or not. Unlike the
-// HostRead functions, this does not raise a user-visible alert on failure.
-std::optional<ReadResult<u8>>
-HostTryReadU8(const Core::CPUThreadGuard& guard, u32 address,
-              RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<ReadResult<u16>>
-HostTryReadU16(const Core::CPUThreadGuard& guard, u32 address,
-               RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<ReadResult<u32>>
-HostTryReadU32(const Core::CPUThreadGuard& guard, u32 address,
-               RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<ReadResult<u64>>
-HostTryReadU64(const Core::CPUThreadGuard& guard, u32 address,
-               RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<ReadResult<float>>
-HostTryReadF32(const Core::CPUThreadGuard& guard, u32 address,
-               RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<ReadResult<double>>
-HostTryReadF64(const Core::CPUThreadGuard& guard, u32 address,
-               RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<ReadResult<u32>>
-HostTryReadInstruction(const Core::CPUThreadGuard& guard, u32 address,
-                       RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<ReadResult<std::string>>
-HostTryReadString(const Core::CPUThreadGuard& guard, u32 address, size_t size = 0,
-                  RequestedAddressSpace space = RequestedAddressSpace::Effective);
-
-// Writes a value to emulated memory using the currently active MMU settings.
-// If the write fails (eg. address does not correspond to a mapped address in the current address
-// space), a PanicAlert will be shown to the user.
-void HostWrite_U8(const Core::CPUThreadGuard& guard, u32 var, u32 address);
-void HostWrite_U16(const Core::CPUThreadGuard& guard, u32 var, u32 address);
-void HostWrite_U32(const Core::CPUThreadGuard& guard, u32 var, u32 address);
-void HostWrite_U64(const Core::CPUThreadGuard& guard, u64 var, u32 address);
-void HostWrite_F32(const Core::CPUThreadGuard& guard, float var, u32 address);
-void HostWrite_F64(const Core::CPUThreadGuard& guard, double var, u32 address);
-
 struct WriteResult
 {
   // whether the address had to be translated (given address was treated as virtual) or not (given
@@ -105,42 +57,21 @@ struct WriteResult
   explicit WriteResult(bool translated_) : translated(translated_) {}
 };
 
-// Try to a write a value to memory at the given address in the given memory space.
-// If the write succeeds, the returned TryWriteResult contains information on whether the given
-// address had to be translated or not. Unlike the HostWrite functions, this does not raise a
-// user-visible alert on failure.
-std::optional<WriteResult>
-HostTryWriteU8(const Core::CPUThreadGuard& guard, u32 var, const u32 address,
-               RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<WriteResult>
-HostTryWriteU16(const Core::CPUThreadGuard& guard, u32 var, const u32 address,
-                RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<WriteResult>
-HostTryWriteU32(const Core::CPUThreadGuard& guard, u32 var, const u32 address,
-                RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<WriteResult>
-HostTryWriteU64(const Core::CPUThreadGuard& guard, u64 var, const u32 address,
-                RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<WriteResult>
-HostTryWriteF32(const Core::CPUThreadGuard& guard, float var, const u32 address,
-                RequestedAddressSpace space = RequestedAddressSpace::Effective);
-std::optional<WriteResult>
-HostTryWriteF64(const Core::CPUThreadGuard& guard, double var, const u32 address,
-                RequestedAddressSpace space = RequestedAddressSpace::Effective);
+constexpr int BAT_INDEX_SHIFT = 17;
+constexpr u32 BAT_PAGE_SIZE = 1 << BAT_INDEX_SHIFT;
+constexpr u32 BAT_PAGE_COUNT = 1 << (32 - BAT_INDEX_SHIFT);
+constexpr u32 BAT_MAPPED_BIT = 0x1;
+constexpr u32 BAT_PHYSICAL_BIT = 0x2;
+constexpr u32 BAT_WI_BIT = 0x4;
+constexpr u32 BAT_RESULT_MASK = UINT32_C(~0x7);
+using BatTable = std::array<u32, BAT_PAGE_COUNT>;  // 128 KB
 
-// Returns whether a read or write to the given address will resolve to a RAM access in the given
-// address space.
-bool HostIsRAMAddress(const Core::CPUThreadGuard& guard, u32 address,
-                      RequestedAddressSpace space = RequestedAddressSpace::Effective);
+constexpr size_t HW_PAGE_SIZE = 4096;
+constexpr size_t HW_PAGE_MASK = HW_PAGE_SIZE - 1;
+constexpr u32 HW_PAGE_INDEX_SHIFT = 12;
+constexpr u32 HW_PAGE_INDEX_MASK = 0x3f;
 
-// Same as HostIsRAMAddress, but uses IBAT instead of DBAT.
-bool HostIsInstructionRAMAddress(const Core::CPUThreadGuard& guard, u32 address,
-                                 RequestedAddressSpace space = RequestedAddressSpace::Effective);
-
-// Routines for the CPU core to access memory.
-
-// Used by interpreter to read instructions, uses iCache
-u32 Read_Opcode(u32 address);
+// Return value of MMU::TryReadInstruction().
 struct TryReadInstResult
 {
   bool valid;
@@ -148,55 +79,8 @@ struct TryReadInstResult
   u32 hex;
   u32 physical_address;
 };
-TryReadInstResult TryReadInstruction(u32 address);
-
-u8 Read_U8(u32 address);
-u16 Read_U16(u32 address);
-u32 Read_U32(u32 address);
-u64 Read_U64(u32 address);
-
-// Useful helper functions, used by ARM JIT
-float Read_F32(u32 address);
-double Read_F64(u32 address);
-
-// used by JIT. Return zero-extended 32bit values
-u32 Read_U8_ZX(u32 address);
-u32 Read_U16_ZX(u32 address);
-
-void Write_U8(u32 var, u32 address);
-void Write_U16(u32 var, u32 address);
-void Write_U32(u32 var, u32 address);
-void Write_U64(u64 var, u32 address);
-
-void Write_U16_Swap(u32 var, u32 address);
-void Write_U32_Swap(u32 var, u32 address);
-void Write_U64_Swap(u64 var, u32 address);
-
-// Useful helper functions, used by ARM JIT
-void Write_F64(double var, u32 address);
-
-void DMA_LCToMemory(u32 mem_address, u32 cache_address, u32 num_blocks);
-void DMA_MemoryToLC(u32 cache_address, u32 mem_address, u32 num_blocks);
-
-void ClearDCacheLine(u32 address);  // Zeroes 32 bytes; address should be 32-byte-aligned
-void StoreDCacheLine(u32 address);
-void InvalidateDCacheLine(u32 address);
-void FlushDCacheLine(u32 address);
-void TouchDCacheLine(u32 address, bool store);
-
-// TLB functions
-void SDRUpdated();
-void InvalidateTLBEntry(u32 address);
-void DBATUpdated();
-void IBATUpdated();
-
-// Result changes based on the BAT registers and MSR.DR.  Returns whether
-// it's safe to optimize a read or write to this address to an unguarded
-// memory access.  Does not consider page tables.
-bool IsOptimizableRAMAddress(u32 address);
-u32 IsOptimizableMMIOAccess(u32 address, u32 access_size);
-bool IsOptimizableGatherPipeWrite(u32 address);
 
+// Return value of MMU::JitCache_TranslateAddress().
 struct TranslateResult
 {
   bool valid = false;
@@ -211,32 +95,261 @@ struct TranslateResult
   {
   }
 };
-TranslateResult JitCache_TranslateAddress(u32 address);
 
-constexpr int BAT_INDEX_SHIFT = 17;
-constexpr u32 BAT_PAGE_SIZE = 1 << BAT_INDEX_SHIFT;
-constexpr u32 BAT_PAGE_COUNT = 1 << (32 - BAT_INDEX_SHIFT);
-constexpr u32 BAT_MAPPED_BIT = 0x1;
-constexpr u32 BAT_PHYSICAL_BIT = 0x2;
-constexpr u32 BAT_WI_BIT = 0x4;
-constexpr u32 BAT_RESULT_MASK = UINT32_C(~0x7);
-using BatTable = std::array<u32, BAT_PAGE_COUNT>;  // 128 KB
-extern BatTable ibat_table;
-extern BatTable dbat_table;
-inline bool TranslateBatAddess(const BatTable& bat_table, u32* address, bool* wi)
+enum class XCheckTLBFlag
 {
-  u32 bat_result = bat_table[*address >> BAT_INDEX_SHIFT];
-  if ((bat_result & BAT_MAPPED_BIT) == 0)
-    return false;
-  *address = (bat_result & BAT_RESULT_MASK) | (*address & (BAT_PAGE_SIZE - 1));
-  *wi = (bat_result & BAT_WI_BIT) != 0;
-  return true;
-}
+  NoException,
+  Read,
+  Write,
+  Opcode,
+  OpcodeNoException
+};
 
-constexpr size_t HW_PAGE_SIZE = 4096;
-constexpr size_t HW_PAGE_MASK = HW_PAGE_SIZE - 1;
-constexpr u32 HW_PAGE_INDEX_SHIFT = 12;
-constexpr u32 HW_PAGE_INDEX_MASK = 0x3f;
+class MMU
+{
+public:
+  MMU(Core::System& system, Memory::MemoryManager& memory, PowerPC::PowerPCState& ppc_state);
+  MMU(const MMU& other) = delete;
+  MMU(MMU&& other) = delete;
+  MMU& operator=(const MMU& other) = delete;
+  MMU& operator=(MMU&& other) = delete;
+  ~MMU();
 
-std::optional<u32> GetTranslatedAddress(u32 address);
+  // Routines for debugger UI, cheats, etc. to access emulated memory from the
+  // perspective of the CPU.  Not for use by core emulation routines.
+  // Use "Host" prefix.
+
+  // Reads a value from emulated memory using the currently active MMU settings.
+  // If the read fails (eg. address does not correspond to a mapped address in the current address
+  // space), a PanicAlert will be shown to the user and zero (or an empty string for the string
+  // case) will be returned.
+  static u8 HostRead_U8(const Core::CPUThreadGuard& guard, u32 address);
+  static u16 HostRead_U16(const Core::CPUThreadGuard& guard, u32 address);
+  static u32 HostRead_U32(const Core::CPUThreadGuard& guard, u32 address);
+  static u64 HostRead_U64(const Core::CPUThreadGuard& guard, u32 address);
+  static float HostRead_F32(const Core::CPUThreadGuard& guard, u32 address);
+  static double HostRead_F64(const Core::CPUThreadGuard& guard, u32 address);
+  static u32 HostRead_Instruction(const Core::CPUThreadGuard& guard, u32 address);
+  static std::string HostGetString(const Core::CPUThreadGuard& guard, u32 address, size_t size = 0);
+
+  // Try to read a value from emulated memory at the given address in the given memory space.
+  // If the read succeeds, the returned value will be present and the ReadResult contains the read
+  // value and information on whether the given address had to be translated or not. Unlike the
+  // HostRead functions, this does not raise a user-visible alert on failure.
+  static std::optional<ReadResult<u8>>
+  HostTryReadU8(const Core::CPUThreadGuard& guard, u32 address,
+                RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<ReadResult<u16>>
+  HostTryReadU16(const Core::CPUThreadGuard& guard, u32 address,
+                 RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<ReadResult<u32>>
+  HostTryReadU32(const Core::CPUThreadGuard& guard, u32 address,
+                 RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<ReadResult<u64>>
+  HostTryReadU64(const Core::CPUThreadGuard& guard, u32 address,
+                 RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<ReadResult<float>>
+  HostTryReadF32(const Core::CPUThreadGuard& guard, u32 address,
+                 RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<ReadResult<double>>
+  HostTryReadF64(const Core::CPUThreadGuard& guard, u32 address,
+                 RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<ReadResult<u32>>
+  HostTryReadInstruction(const Core::CPUThreadGuard& guard, u32 address,
+                         RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<ReadResult<std::string>>
+  HostTryReadString(const Core::CPUThreadGuard& guard, u32 address, size_t size = 0,
+                    RequestedAddressSpace space = RequestedAddressSpace::Effective);
+
+  // Writes a value to emulated memory using the currently active MMU settings.
+  // If the write fails (eg. address does not correspond to a mapped address in the current address
+  // space), a PanicAlert will be shown to the user.
+  static void HostWrite_U8(const Core::CPUThreadGuard& guard, u32 var, u32 address);
+  static void HostWrite_U16(const Core::CPUThreadGuard& guard, u32 var, u32 address);
+  static void HostWrite_U32(const Core::CPUThreadGuard& guard, u32 var, u32 address);
+  static void HostWrite_U64(const Core::CPUThreadGuard& guard, u64 var, u32 address);
+  static void HostWrite_F32(const Core::CPUThreadGuard& guard, float var, u32 address);
+  static void HostWrite_F64(const Core::CPUThreadGuard& guard, double var, u32 address);
+
+  // Try to a write a value to memory at the given address in the given memory space.
+  // If the write succeeds, the returned TryWriteResult contains information on whether the given
+  // address had to be translated or not. Unlike the HostWrite functions, this does not raise a
+  // user-visible alert on failure.
+  static std::optional<WriteResult>
+  HostTryWriteU8(const Core::CPUThreadGuard& guard, u32 var, const u32 address,
+                 RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<WriteResult>
+  HostTryWriteU16(const Core::CPUThreadGuard& guard, u32 var, const u32 address,
+                  RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<WriteResult>
+  HostTryWriteU32(const Core::CPUThreadGuard& guard, u32 var, const u32 address,
+                  RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<WriteResult>
+  HostTryWriteU64(const Core::CPUThreadGuard& guard, u64 var, const u32 address,
+                  RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<WriteResult>
+  HostTryWriteF32(const Core::CPUThreadGuard& guard, float var, const u32 address,
+                  RequestedAddressSpace space = RequestedAddressSpace::Effective);
+  static std::optional<WriteResult>
+  HostTryWriteF64(const Core::CPUThreadGuard& guard, double var, const u32 address,
+                  RequestedAddressSpace space = RequestedAddressSpace::Effective);
+
+  // Returns whether a read or write to the given address will resolve to a RAM access in the given
+  // address space.
+  static bool HostIsRAMAddress(const Core::CPUThreadGuard& guard, u32 address,
+                               RequestedAddressSpace space = RequestedAddressSpace::Effective);
+
+  // Same as HostIsRAMAddress, but uses IBAT instead of DBAT.
+  static bool
+  HostIsInstructionRAMAddress(const Core::CPUThreadGuard& guard, u32 address,
+                              RequestedAddressSpace space = RequestedAddressSpace::Effective);
+
+  // Routines for the CPU core to access memory.
+
+  // Used by interpreter to read instructions, uses iCache
+  u32 Read_Opcode(u32 address);
+  TryReadInstResult TryReadInstruction(u32 address);
+
+  u8 Read_U8(u32 address);
+  u16 Read_U16(u32 address);
+  u32 Read_U32(u32 address);
+  u64 Read_U64(u32 address);
+
+  void Write_U8(u32 var, u32 address);
+  void Write_U16(u32 var, u32 address);
+  void Write_U32(u32 var, u32 address);
+  void Write_U64(u64 var, u32 address);
+
+  void Write_U16_Swap(u32 var, u32 address);
+  void Write_U32_Swap(u32 var, u32 address);
+  void Write_U64_Swap(u64 var, u32 address);
+
+  void DMA_LCToMemory(u32 mem_address, u32 cache_address, u32 num_blocks);
+  void DMA_MemoryToLC(u32 cache_address, u32 mem_address, u32 num_blocks);
+
+  void ClearDCacheLine(u32 address);  // Zeroes 32 bytes; address should be 32-byte-aligned
+  void StoreDCacheLine(u32 address);
+  void InvalidateDCacheLine(u32 address);
+  void FlushDCacheLine(u32 address);
+  void TouchDCacheLine(u32 address, bool store);
+
+  // TLB functions
+  void SDRUpdated();
+  void InvalidateTLBEntry(u32 address);
+  void DBATUpdated();
+  void IBATUpdated();
+
+  // Result changes based on the BAT registers and MSR.DR.  Returns whether
+  // it's safe to optimize a read or write to this address to an unguarded
+  // memory access.  Does not consider page tables.
+  bool IsOptimizableRAMAddress(u32 address) const;
+  u32 IsOptimizableMMIOAccess(u32 address, u32 access_size) const;
+  bool IsOptimizableGatherPipeWrite(u32 address) const;
+
+  TranslateResult JitCache_TranslateAddress(u32 address);
+
+  std::optional<u32> GetTranslatedAddress(u32 address);
+
+  BatTable& GetIBATTable() { return m_ibat_table; }
+  BatTable& GetDBATTable() { return m_dbat_table; }
+
+private:
+  enum class TranslateAddressResultEnum : u8
+  {
+    BAT_TRANSLATED,
+    PAGE_TABLE_TRANSLATED,
+    DIRECT_STORE_SEGMENT,
+    PAGE_FAULT,
+  };
+
+  struct TranslateAddressResult
+  {
+    u32 address;
+    TranslateAddressResultEnum result;
+    bool wi;  // Set to true if the view of memory is either write-through or cache-inhibited
+
+    TranslateAddressResult(TranslateAddressResultEnum result_, u32 address_, bool wi_ = false)
+        : address(address_), result(result_), wi(wi_)
+    {
+    }
+    bool Success() const { return result <= TranslateAddressResultEnum::PAGE_TABLE_TRANSLATED; }
+  };
+
+  union EffectiveAddress
+  {
+    BitField<0, 12, u32> offset;
+    BitField<12, 16, u32> page_index;
+    BitField<22, 6, u32> API;
+    BitField<28, 4, u32> SR;
+
+    u32 Hex = 0;
+
+    EffectiveAddress() = default;
+    explicit EffectiveAddress(u32 address) : Hex{address} {}
+  };
+
+  template <const XCheckTLBFlag flag>
+  TranslateAddressResult TranslateAddress(u32 address);
+
+  TranslateAddressResult TranslatePageAddress(const EffectiveAddress address,
+                                              const XCheckTLBFlag flag, bool* wi);
+
+  void GenerateDSIException(u32 effective_address, bool write);
+  void GenerateISIException(u32 effective_address);
+
+  void Memcheck(u32 address, u64 var, bool write, size_t size);
+
+  void UpdateBATs(BatTable& bat_table, u32 base_spr);
+  void UpdateFakeMMUBat(BatTable& bat_table, u32 start_addr);
+
+  template <XCheckTLBFlag flag, typename T, bool never_translate = false>
+  T ReadFromHardware(u32 em_address);
+  template <XCheckTLBFlag flag, bool never_translate = false>
+  void WriteToHardware(u32 em_address, const u32 data, const u32 size);
+  template <XCheckTLBFlag flag>
+  bool IsRAMAddress(u32 address, bool translate);
+
+  template <typename T>
+  static std::optional<ReadResult<T>> HostTryReadUX(const Core::CPUThreadGuard& guard,
+                                                    const u32 address, RequestedAddressSpace space);
+  static std::optional<WriteResult> HostTryWriteUX(const Core::CPUThreadGuard& guard, const u32 var,
+                                                   const u32 address, const u32 size,
+                                                   RequestedAddressSpace space);
+
+  Core::System& m_system;
+  Memory::MemoryManager& m_memory;
+  PowerPC::PowerPCState& m_ppc_state;
+
+  BatTable m_ibat_table;
+  BatTable m_dbat_table;
+};
+
+void ClearDCacheLineFromJit64(MMU& mmu, u32 address);
+u32 ReadU8ZXFromJit64(MMU& mmu, u32 address);   // Returns zero-extended 32bit value
+u32 ReadU16ZXFromJit64(MMU& mmu, u32 address);  // Returns zero-extended 32bit value
+u32 ReadU32FromJit64(MMU& mmu, u32 address);
+u64 ReadU64FromJit64(MMU& mmu, u32 address);
+void WriteU8FromJit64(MMU& mmu, u32 var, u32 address);
+void WriteU16FromJit64(MMU& mmu, u32 var, u32 address);
+void WriteU32FromJit64(MMU& mmu, u32 var, u32 address);
+void WriteU64FromJit64(MMU& mmu, u64 var, u32 address);
+void WriteU16SwapFromJit64(MMU& mmu, u32 var, u32 address);
+void WriteU32SwapFromJit64(MMU& mmu, u32 var, u32 address);
+void WriteU64SwapFromJit64(MMU& mmu, u64 var, u32 address);
+
+// The JitArm64 function that calls these has very specific register allocation that's difficult to
+// change, so we have a separate set of functions here for it. This can probably be refactored in
+// the future.
+void ClearDCacheLineFromJitArm64(u32 address, MMU& mmu);
+u8 ReadU8FromJitArm64(u32 address, MMU& mmu);
+u16 ReadU16FromJitArm64(u32 address, MMU& mmu);
+u32 ReadU32FromJitArm64(u32 address, MMU& mmu);
+u64 ReadU64FromJitArm64(u32 address, MMU& mmu);
+void WriteU8FromJitArm64(u32 var, u32 address, MMU& mmu);
+void WriteU16FromJitArm64(u32 var, u32 address, MMU& mmu);
+void WriteU32FromJitArm64(u32 var, u32 address, MMU& mmu);
+void WriteU64FromJitArm64(u64 var, u32 address, MMU& mmu);
+void WriteU16SwapFromJitArm64(u32 var, u32 address, MMU& mmu);
+void WriteU32SwapFromJitArm64(u32 var, u32 address, MMU& mmu);
+void WriteU64SwapFromJitArm64(u64 var, u32 address, MMU& mmu);
 }  // namespace PowerPC
diff --git a/Source/Core/Core/PowerPC/PPCAnalyst.cpp b/Source/Core/Core/PowerPC/PPCAnalyst.cpp
index 0a04b8b9d9..1d4f5fab2f 100644
--- a/Source/Core/Core/PowerPC/PPCAnalyst.cpp
+++ b/Source/Core/Core/PowerPC/PPCAnalyst.cpp
@@ -17,12 +17,14 @@
 #include "Common/StringUtil.h"
 #include "Core/Config/MainSettings.h"
 #include "Core/ConfigManager.h"
+#include "Core/Core.h"
 #include "Core/PowerPC/JitCommon/JitBase.h"
 #include "Core/PowerPC/MMU.h"
 #include "Core/PowerPC/PPCSymbolDB.h"
 #include "Core/PowerPC/PPCTables.h"
 #include "Core/PowerPC/PowerPC.h"
 #include "Core/PowerPC/SignatureDB/SignatureDB.h"
+#include "Core/System.h"
 
 // Analyzes PowerPC code in memory to find functions
 // After running, for each function we will know what functions it calls
@@ -82,6 +84,8 @@ bool AnalyzeFunction(const Core::CPUThreadGuard& guard, u32 startAddr, Common::S
   if (func.analyzed)
     return true;  // No error, just already did it.
 
+  auto& mmu = guard.GetSystem().GetMMU();
+
   func.calls.clear();
   func.callers.clear();
   func.size = 0;
@@ -93,7 +97,7 @@ bool AnalyzeFunction(const Core::CPUThreadGuard& guard, u32 startAddr, Common::S
   {
     func.size += 4;
     if (func.size >= JitBase::code_buffer_size * 4 ||
-        !PowerPC::HostIsInstructionRAMAddress(guard, addr))
+        !PowerPC::MMU::HostIsInstructionRAMAddress(guard, addr))
     {
       return false;
     }
@@ -108,7 +112,7 @@ bool AnalyzeFunction(const Core::CPUThreadGuard& guard, u32 startAddr, Common::S
         func.flags |= Common::FFLAG_STRAIGHT;
       return true;
     }
-    const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
+    const PowerPC::TryReadInstResult read_result = mmu.TryReadInstruction(addr);
     const UGeckoInstruction instr = read_result.hex;
     if (read_result.valid && PPCTables::IsValidInstruction(instr))
     {
@@ -264,9 +268,10 @@ bool PPCAnalyzer::CanSwapAdjacentOps(const CodeOp& a, const CodeOp& b) const
 static void FindFunctionsFromBranches(const Core::CPUThreadGuard& guard, u32 startAddr, u32 endAddr,
                                       Common::SymbolDB* func_db)
 {
+  auto& mmu = guard.GetSystem().GetMMU();
   for (u32 addr = startAddr; addr < endAddr; addr += 4)
   {
-    const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
+    const PowerPC::TryReadInstResult read_result = mmu.TryReadInstruction(addr);
     const UGeckoInstruction instr = read_result.hex;
 
     if (read_result.valid && PPCTables::IsValidInstruction(instr))
@@ -280,7 +285,7 @@ static void FindFunctionsFromBranches(const Core::CPUThreadGuard& guard, u32 sta
           u32 target = SignExt26(instr.LI << 2);
           if (!instr.AA)
             target += addr;
-          if (PowerPC::HostIsRAMAddress(guard, target))
+          if (PowerPC::MMU::HostIsRAMAddress(guard, target))
           {
             func_db->AddFunction(guard, target);
           }
@@ -314,9 +319,10 @@ static void FindFunctionsFromHandlers(const Core::CPUThreadGuard& guard, PPCSymb
       {0x80001400, "system_management_interrupt_handler"},
       {0x80001700, "thermal_management_interrupt_exception_handler"}};
 
+  auto& mmu = guard.GetSystem().GetMMU();
   for (const auto& entry : handlers)
   {
-    const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(entry.first);
+    const PowerPC::TryReadInstResult read_result = mmu.TryReadInstruction(entry.first);
     if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex))
     {
       // Check if this function is already mapped
@@ -336,19 +342,20 @@ static void FindFunctionsAfterReturnInstruction(const Core::CPUThreadGuard& guar
   for (const auto& func : func_db->Symbols())
     funcAddrs.push_back(func.second.address + func.second.size);
 
+  auto& mmu = guard.GetSystem().GetMMU();
   for (u32& location : funcAddrs)
   {
     while (true)
     {
       // Skip zeroes (e.g. Donkey Kong Country Returns) and nop (e.g. libogc)
       // that sometimes pad function to 16 byte boundary.
-      PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(location);
+      PowerPC::TryReadInstResult read_result = mmu.TryReadInstruction(location);
       while (read_result.valid && (location & 0xf) != 0)
       {
         if (read_result.hex != 0 && read_result.hex != 0x60000000)
           break;
         location += 4;
-        read_result = PowerPC::TryReadInstruction(location);
+        read_result = mmu.TryReadInstruction(location);
       }
       if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex))
       {
@@ -757,9 +764,10 @@ u32 PPCAnalyzer::Analyze(u32 address, CodeBlock* block, CodeBuffer* buffer,
 
   const bool enable_follow = m_enable_branch_following;
 
+  auto& mmu = Core::System::GetInstance().GetMMU();
   for (std::size_t i = 0; i < block_size; ++i)
   {
-    auto result = PowerPC::TryReadInstruction(address);
+    auto result = mmu.TryReadInstruction(address);
     if (!result.valid)
     {
       if (i == 0)
diff --git a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp
index c9ac1e7427..61e620248d 100644
--- a/Source/Core/Core/PowerPC/PPCSymbolDB.cpp
+++ b/Source/Core/Core/PowerPC/PPCSymbolDB.cpp
@@ -18,10 +18,12 @@
 #include "Common/IOFile.h"
 #include "Common/Logging/Log.h"
 #include "Common/StringUtil.h"
+#include "Core/Core.h"
 #include "Core/PowerPC/MMU.h"
 #include "Core/PowerPC/PPCAnalyst.h"
 #include "Core/PowerPC/PowerPC.h"
 #include "Core/PowerPC/SignatureDB/SignatureDB.h"
+#include "Core/System.h"
 
 PPCSymbolDB g_symbolDB;
 
@@ -407,16 +409,17 @@ bool PPCSymbolDB::LoadMap(const Core::CPUThreadGuard& guard, const std::string&
     if (strlen(name) > 0)
     {
       // Can't compute the checksum if not in RAM
-      bool good = !bad && PowerPC::HostIsInstructionRAMAddress(guard, vaddress) &&
-                  PowerPC::HostIsInstructionRAMAddress(guard, vaddress + size - 4);
+      bool good = !bad && PowerPC::MMU::HostIsInstructionRAMAddress(guard, vaddress) &&
+                  PowerPC::MMU::HostIsInstructionRAMAddress(guard, vaddress + size - 4);
       if (!good)
       {
         // check for BLR before function
-        PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(vaddress - 4);
+        PowerPC::TryReadInstResult read_result =
+            guard.GetSystem().GetMMU().TryReadInstruction(vaddress - 4);
         if (read_result.valid && read_result.hex == 0x4e800020)
         {
           // check for BLR at end of function
-          read_result = PowerPC::TryReadInstruction(vaddress + size - 4);
+          read_result = guard.GetSystem().GetMMU().TryReadInstruction(vaddress + size - 4);
           good = read_result.valid && read_result.hex == 0x4e800020;
         }
       }
diff --git a/Source/Core/Core/PowerPC/PowerPC.cpp b/Source/Core/Core/PowerPC/PowerPC.cpp
index b18960f473..fef24a482a 100644
--- a/Source/Core/Core/PowerPC/PowerPC.cpp
+++ b/Source/Core/Core/PowerPC/PowerPC.cpp
@@ -146,8 +146,10 @@ void DoState(PointerWrap& p)
     }
 
     RoundingModeUpdated();
-    IBATUpdated();
-    DBATUpdated();
+
+    auto& mmu = Core::System::GetInstance().GetMMU();
+    mmu.IBATUpdated();
+    mmu.DBATUpdated();
   }
 
   // SystemTimers::DecrementerSet();
@@ -202,8 +204,10 @@ static void ResetRegisters()
   ppcState.SetXER({});
 
   RoundingModeUpdated();
-  DBATUpdated();
-  IBATUpdated();
+
+  auto& mmu = Core::System::GetInstance().GetMMU();
+  mmu.DBATUpdated();
+  mmu.IBATUpdated();
 
   TL(PowerPC::ppcState) = 0;
   TU(PowerPC::ppcState) = 0;
diff --git a/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp b/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp
index 4d7c579a01..a6aefc088b 100644
--- a/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp
+++ b/Source/Core/Core/PowerPC/SignatureDB/MEGASignatureDB.cpp
@@ -108,8 +108,8 @@ bool Compare(const Core::CPUThreadGuard& guard, u32 address, u32 size, const MEG
 
   for (size_t i = 0; i < sig.code.size(); ++i)
   {
-    if (sig.code[i] != 0 &&
-        PowerPC::HostRead_U32(guard, static_cast<u32>(address + i * sizeof(u32))) != sig.code[i])
+    if (sig.code[i] != 0 && PowerPC::MMU::HostRead_U32(
+                                guard, static_cast<u32>(address + i * sizeof(u32))) != sig.code[i])
     {
       return false;
     }
diff --git a/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp b/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp
index afe09acfa2..b9cec4a2be 100644
--- a/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp
+++ b/Source/Core/Core/PowerPC/SignatureDB/SignatureDB.cpp
@@ -166,7 +166,7 @@ u32 HashSignatureDB::ComputeCodeChecksum(const Core::CPUThreadGuard& guard, u32
   u32 sum = 0;
   for (u32 offset = offsetStart; offset <= offsetEnd; offset += 4)
   {
-    u32 opcode = PowerPC::HostRead_Instruction(guard, offset);
+    u32 opcode = PowerPC::MMU::HostRead_Instruction(guard, offset);
     u32 op = opcode & 0xFC000000;
     u32 op2 = 0;
     u32 op3 = 0;
diff --git a/Source/Core/Core/System.cpp b/Source/Core/Core/System.cpp
index 1897131041..438b3c3014 100644
--- a/Source/Core/Core/System.cpp
+++ b/Source/Core/Core/System.cpp
@@ -40,8 +40,9 @@ struct System::Impl
   explicit Impl(System& system)
       : m_audio_interface(system), m_core_timing(system), m_dsp(system), m_dvd_interface(system),
         m_dvd_thread(system), m_expansion_interface(system), m_gp_fifo(system), m_memory(system),
-        m_ppc_state(PowerPC::ppcState), m_processor_interface(system), m_serial_interface(system),
-        m_video_interface(system), m_interpreter(system, m_ppc_state), m_jit_interface(system)
+        m_ppc_state(PowerPC::ppcState), m_mmu(system, m_memory, m_ppc_state),
+        m_processor_interface(system), m_serial_interface(system), m_video_interface(system),
+        m_interpreter(system, m_ppc_state, m_mmu), m_jit_interface(system)
   {
   }
 
@@ -67,6 +68,7 @@ struct System::Impl
   PixelEngine::PixelEngineManager m_pixel_engine;
   PixelShaderManager m_pixel_shader_manager;
   PowerPC::PowerPCState& m_ppc_state;
+  PowerPC::MMU m_mmu;
   ProcessorInterface::ProcessorInterfaceManager m_processor_interface;
   SerialInterface::SerialInterfaceManager m_serial_interface;
   Sram m_sram;
@@ -204,6 +206,11 @@ MemoryInterface::MemoryInterfaceManager& System::GetMemoryInterface() const
   return m_impl->m_memory_interface;
 }
 
+PowerPC::MMU& System::GetMMU() const
+{
+  return m_impl->m_mmu;
+}
+
 PixelEngine::PixelEngineManager& System::GetPixelEngine() const
 {
   return m_impl->m_pixel_engine;
diff --git a/Source/Core/Core/System.h b/Source/Core/Core/System.h
index d25b5ad609..6a6375f0d9 100644
--- a/Source/Core/Core/System.h
+++ b/Source/Core/Core/System.h
@@ -72,8 +72,9 @@ class PixelEngineManager;
 };
 namespace PowerPC
 {
+class MMU;
 struct PowerPCState;
-}
+}  // namespace PowerPC
 namespace ProcessorInterface
 {
 class ProcessorInterfaceManager;
@@ -138,6 +139,7 @@ public:
   IOS::HLE::USB::SkylanderPortal& GetSkylanderPortal() const;
   Memory::MemoryManager& GetMemory() const;
   MemoryInterface::MemoryInterfaceManager& GetMemoryInterface() const;
+  PowerPC::MMU& GetMMU() const;
   PixelEngine::PixelEngineManager& GetPixelEngine() const;
   PixelShaderManager& GetPixelShaderManager() const;
   PowerPC::PowerPCState& GetPPCState() const;
diff --git a/Source/Core/DiscIO/RiivolutionPatcher.cpp b/Source/Core/DiscIO/RiivolutionPatcher.cpp
index 0f9bbec3ca..309e9e5ea1 100644
--- a/Source/Core/DiscIO/RiivolutionPatcher.cpp
+++ b/Source/Core/DiscIO/RiivolutionPatcher.cpp
@@ -502,7 +502,7 @@ static bool MemoryMatchesAt(const Core::CPUThreadGuard& guard, u32 offset,
 {
   for (u32 i = 0; i < value.size(); ++i)
   {
-    auto result = PowerPC::HostTryReadU8(guard, offset + i);
+    auto result = PowerPC::MMU::HostTryReadU8(guard, offset + i);
     if (!result || result->value != value[i])
       return false;
   }
@@ -521,7 +521,7 @@ static void ApplyMemoryPatch(const Core::CPUThreadGuard& guard, u32 offset,
   auto& system = Core::System::GetInstance();
   const u32 size = static_cast<u32>(value.size());
   for (u32 i = 0; i < size; ++i)
-    PowerPC::HostTryWriteU8(guard, value[i], offset + i);
+    PowerPC::MMU::HostTryWriteU8(guard, value[i], offset + i);
   const u32 overlapping_hook_count = HLE::UnpatchRange(system, offset, offset + size);
   if (overlapping_hook_count != 0)
   {
@@ -585,13 +585,13 @@ static void ApplyOcarinaMemoryPatch(const Core::CPUThreadGuard& guard, const Pat
       {
         // from the pattern find the next blr instruction
         const u32 blr_address = ram_start + i;
-        auto blr = PowerPC::HostTryReadU32(guard, blr_address);
+        auto blr = PowerPC::MMU::HostTryReadU32(guard, blr_address);
         if (blr && blr->value == 0x4e800020)
         {
           // and replace it with a jump to the given offset
           const u32 target = memory_patch.m_offset | 0x80000000;
           const u32 jmp = ((target - blr_address) & 0x03fffffc) | 0x48000000;
-          PowerPC::HostTryWriteU32(guard, jmp, blr_address);
+          PowerPC::MMU::HostTryWriteU32(guard, jmp, blr_address);
           const u32 overlapping_hook_count =
               HLE::UnpatchRange(system, blr_address, blr_address + 4);
           if (overlapping_hook_count != 0)
diff --git a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
index e4073fb16a..f954f57ccc 100644
--- a/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/CodeViewWidget.cpp
@@ -987,10 +987,11 @@ void CodeViewWidget::OnReplaceInstruction()
 
   const u32 addr = GetContextAddress();
 
-  if (!PowerPC::HostIsInstructionRAMAddress(guard, addr))
+  if (!PowerPC::MMU::HostIsInstructionRAMAddress(guard, addr))
     return;
 
-  const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
+  const PowerPC::TryReadInstResult read_result =
+      guard.GetSystem().GetMMU().TryReadInstruction(addr);
   if (!read_result.valid)
     return;
 
diff --git a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp
index d232fede85..29d72faf18 100644
--- a/Source/Core/DolphinQt/Debugger/CodeWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/CodeWidget.cpp
@@ -463,7 +463,7 @@ void CodeWidget::StepOver()
 
   const UGeckoInstruction inst = [&] {
     Core::CPUThreadGuard guard(system);
-    return PowerPC::HostRead_Instruction(guard, PowerPC::ppcState.pc);
+    return PowerPC::MMU::HostRead_Instruction(guard, PowerPC::ppcState.pc);
   }();
 
   if (inst.LK)
@@ -516,7 +516,7 @@ void CodeWidget::StepOut()
     // Loop until either the current instruction is a return instruction with no Link flag
     // or a breakpoint is detected so it can step at the breakpoint. If the PC is currently
     // on a breakpoint, skip it.
-    UGeckoInstruction inst = PowerPC::HostRead_Instruction(guard, PowerPC::ppcState.pc);
+    UGeckoInstruction inst = PowerPC::MMU::HostRead_Instruction(guard, PowerPC::ppcState.pc);
     do
     {
       if (WillInstructionReturn(inst))
@@ -540,7 +540,7 @@ void CodeWidget::StepOut()
         PowerPC::SingleStep();
       }
 
-      inst = PowerPC::HostRead_Instruction(guard, PowerPC::ppcState.pc);
+      inst = PowerPC::MMU::HostRead_Instruction(guard, PowerPC::ppcState.pc);
     } while (clock::now() < timeout &&
              !PowerPC::breakpoints.IsAddressBreakPoint(PowerPC::ppcState.pc));
 
diff --git a/Source/Core/DolphinQt/Debugger/ThreadWidget.cpp b/Source/Core/DolphinQt/Debugger/ThreadWidget.cpp
index 147efa3779..3905700f2b 100644
--- a/Source/Core/DolphinQt/Debugger/ThreadWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/ThreadWidget.cpp
@@ -268,7 +268,8 @@ void ThreadWidget::Update()
     return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0'));
   };
   const auto format_hex_from = [&format_hex](const Core::CPUThreadGuard& guard, u32 addr) {
-    addr = PowerPC::HostIsRAMAddress(guard, addr) ? PowerPC::HostRead_U32(guard, addr) : 0;
+    addr =
+        PowerPC::MMU::HostIsRAMAddress(guard, addr) ? PowerPC::MMU::HostRead_U32(guard, addr) : 0;
     return format_hex(addr);
   };
   const auto get_state = [](u16 thread_state) {
@@ -449,13 +450,13 @@ void ThreadWidget::UpdateThreadCallstack(const Core::CPUThreadGuard& guard,
   u32 sp = context.gpr->at(1);
   for (int i = 0; i < 16; i++)
   {
-    if (sp == 0 || sp == 0xffffffff || !PowerPC::HostIsRAMAddress(guard, sp))
+    if (sp == 0 || sp == 0xffffffff || !PowerPC::MMU::HostIsRAMAddress(guard, sp))
       break;
     m_callstack_table->insertRow(i);
     m_callstack_table->setItem(i, 0, new QTableWidgetItem(format_hex(sp)));
-    if (PowerPC::HostIsRAMAddress(guard, sp + 4))
+    if (PowerPC::MMU::HostIsRAMAddress(guard, sp + 4))
     {
-      const u32 lr_save = PowerPC::HostRead_U32(guard, sp + 4);
+      const u32 lr_save = PowerPC::MMU::HostRead_U32(guard, sp + 4);
       m_callstack_table->setItem(i, 2, new QTableWidgetItem(format_hex(lr_save)));
       m_callstack_table->setItem(i, 3,
                                  new QTableWidgetItem(QString::fromStdString(
@@ -465,7 +466,7 @@ void ThreadWidget::UpdateThreadCallstack(const Core::CPUThreadGuard& guard,
     {
       m_callstack_table->setItem(i, 2, new QTableWidgetItem(QStringLiteral("--------")));
     }
-    sp = PowerPC::HostRead_U32(guard, sp);
+    sp = PowerPC::MMU::HostRead_U32(guard, sp);
     m_callstack_table->setItem(i, 1, new QTableWidgetItem(format_hex(sp)));
   }
 }
diff --git a/Source/Core/DolphinQt/Debugger/WatchWidget.cpp b/Source/Core/DolphinQt/Debugger/WatchWidget.cpp
index 8bab9ed1fb..fdb1279072 100644
--- a/Source/Core/DolphinQt/Debugger/WatchWidget.cpp
+++ b/Source/Core/DolphinQt/Debugger/WatchWidget.cpp
@@ -193,18 +193,19 @@ void WatchWidget::Update()
 
     QBrush brush = QPalette().brush(QPalette::Text);
 
-    if (!Core::IsRunning() || !PowerPC::HostIsRAMAddress(guard, entry.address))
+    if (!Core::IsRunning() || !PowerPC::MMU::HostIsRAMAddress(guard, entry.address))
       brush.setColor(Qt::red);
 
     if (Core::IsRunning())
     {
-      if (PowerPC::HostIsRAMAddress(guard, entry.address))
+      if (PowerPC::MMU::HostIsRAMAddress(guard, entry.address))
       {
-        hex->setText(QStringLiteral("%1").arg(PowerPC::HostRead_U32(guard, entry.address), 8, 16,
-                                              QLatin1Char('0')));
-        decimal->setText(QString::number(PowerPC::HostRead_U32(guard, entry.address)));
-        string->setText(QString::fromStdString(PowerPC::HostGetString(guard, entry.address, 32)));
-        floatValue->setText(QString::number(PowerPC::HostRead_F32(guard, entry.address)));
+        hex->setText(QStringLiteral("%1").arg(PowerPC::MMU::HostRead_U32(guard, entry.address), 8,
+                                              16, QLatin1Char('0')));
+        decimal->setText(QString::number(PowerPC::MMU::HostRead_U32(guard, entry.address)));
+        string->setText(
+            QString::fromStdString(PowerPC::MMU::HostGetString(guard, entry.address, 32)));
+        floatValue->setText(QString::number(PowerPC::MMU::HostRead_F32(guard, entry.address)));
         lockValue->setCheckState(entry.locked ? Qt::Checked : Qt::Unchecked);
       }
     }
@@ -418,7 +419,7 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item)
         }
         else
         {
-          PowerPC::HostWrite_U32(guard, value, PowerPC::debug_interface.GetWatch(row).address);
+          PowerPC::MMU::HostWrite_U32(guard, value, PowerPC::debug_interface.GetWatch(row).address);
         }
       }
       else
@@ -446,7 +447,7 @@ void WatchWidget::OnItemChanged(QTableWidgetItem* item)
 
 void WatchWidget::LockWatchAddress(const Core::CPUThreadGuard& guard, u32 address)
 {
-  const std::string memory_data_as_string = PowerPC::HostGetString(guard, address, 4);
+  const std::string memory_data_as_string = PowerPC::MMU::HostGetString(guard, address, 4);
 
   std::vector<u8> bytes;
   for (const char c : memory_data_as_string)
diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp
index ea991d1e6f..2677ac27d0 100644
--- a/Source/Core/DolphinQt/MenuBar.cpp
+++ b/Source/Core/DolphinQt/MenuBar.cpp
@@ -1351,7 +1351,7 @@ RSOVector MenuBar::DetectRSOModules(ParallelProgressDialog& progress)
 
         for (; len < MODULE_NAME_MAX_LENGTH; ++len)
         {
-          const auto res = PowerPC::HostRead_U8(guard, *found_addr - (len + 1));
+          const auto res = PowerPC::MMU::HostRead_U8(guard, *found_addr - (len + 1));
           if (!std::isprint(res))
           {
             break;
@@ -1406,8 +1406,8 @@ RSOVector MenuBar::DetectRSOModules(ParallelProgressDialog& progress)
       const auto module_name_offset = accessors->ReadU32(guard, *found_addr);
 
       // Go to the beginning of the RSO header
-      matches.emplace_back(*found_addr - 16,
-                           PowerPC::HostGetString(guard, module_name_offset, module_name_length));
+      matches.emplace_back(*found_addr - 16, PowerPC::MMU::HostGetString(guard, module_name_offset,
+                                                                         module_name_length));
 
       progress.SetLabelText(tr("Modules found: %1").arg(matches.size()));
     }
@@ -1698,8 +1698,8 @@ void MenuBar::SearchInstruction()
   for (u32 addr = Memory::MEM1_BASE_ADDR; addr < Memory::MEM1_BASE_ADDR + memory.GetRamSizeReal();
        addr += 4)
   {
-    const auto ins_name =
-        QString::fromStdString(PPCTables::GetInstructionName(PowerPC::HostRead_U32(guard, addr)));
+    const auto ins_name = QString::fromStdString(
+        PPCTables::GetInstructionName(PowerPC::MMU::HostRead_U32(guard, addr)));
     if (op == ins_name)
     {
       NOTICE_LOG_FMT(POWERPC, "Found {} at {:08x}", op.toStdString(), addr);