From fc4b7cba2c083b3920f2d74e0cb4b08cf7a5a278 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Wed, 4 Jan 2023 20:01:44 -0300
Subject: [PATCH] Make PPTC state non-static (#4157)

* Make PPTC state non-static

* DiskCacheLoadState can be null
---
 ARMeilleure/Translation/ArmEmitterContext.cs  |   7 +-
 ARMeilleure/Translation/PTC/IPtcLoadState.cs  |  10 ++
 ARMeilleure/Translation/PTC/Ptc.cs            | 136 +++++++++---------
 ARMeilleure/Translation/PTC/PtcProfiler.cs    |  74 +++++-----
 ARMeilleure/Translation/Translator.cs         |  46 ++++--
 Ryujinx.Ava/AppHost.cs                        |   7 +-
 Ryujinx.Ava/Program.cs                        |   7 -
 .../UI/ViewModels/MainWindowViewModel.cs      |  25 ++--
 Ryujinx.Cpu/ICpuContext.cs                    |  22 +++
 Ryujinx.Cpu/IDiskCacheState.cs                |  20 +++
 Ryujinx.Cpu/Jit/JitCpuContext.cs              |  12 ++
 Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs      |  38 +++++
 Ryujinx.Cpu/LoadState.cs                      |  12 ++
 Ryujinx.HLE/HOS/ApplicationLoader.cs          |  55 ++++---
 Ryujinx.HLE/HOS/ArmProcessContext.cs          |  23 ++-
 Ryujinx.HLE/HOS/ArmProcessContextFactory.cs   |  33 ++++-
 Ryujinx.HLE/HOS/ProgramLoader.cs              |  90 ++++++++----
 Ryujinx.Headless.SDL2/Program.cs              |  14 +-
 Ryujinx/Program.cs                            |   7 -
 Ryujinx/Ui/MainWindow.cs                      |  21 ++-
 Ryujinx/Ui/RendererWidgetBase.cs              |   5 +-
 21 files changed, 434 insertions(+), 230 deletions(-)
 create mode 100644 ARMeilleure/Translation/PTC/IPtcLoadState.cs
 create mode 100644 Ryujinx.Cpu/IDiskCacheState.cs
 create mode 100644 Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs
 create mode 100644 Ryujinx.Cpu/LoadState.cs

diff --git a/ARMeilleure/Translation/ArmEmitterContext.cs b/ARMeilleure/Translation/ArmEmitterContext.cs
index 33355daec..48254de4e 100644
--- a/ARMeilleure/Translation/ArmEmitterContext.cs
+++ b/ARMeilleure/Translation/ArmEmitterContext.cs
@@ -6,7 +6,6 @@ using ARMeilleure.Instructions;
 using ARMeilleure.IntermediateRepresentation;
 using ARMeilleure.Memory;
 using ARMeilleure.State;
-using ARMeilleure.Translation.PTC;
 using System;
 using System.Collections.Generic;
 using System.Reflection;
@@ -44,14 +43,13 @@ namespace ARMeilleure.Translation
 
         public IMemoryManager Memory { get; }
 
-        public bool HasPtc { get; }
-
         public EntryTable<uint> CountTable { get; }
         public AddressTable<ulong> FunctionTable { get; }
         public TranslatorStubs Stubs { get; }
 
         public ulong EntryAddress { get; }
         public bool HighCq { get; }
+        public bool HasPtc { get; }
         public Aarch32Mode Mode { get; }
 
         private int _ifThenBlockStateIndex = 0;
@@ -66,15 +64,16 @@ namespace ARMeilleure.Translation
             TranslatorStubs stubs,
             ulong entryAddress,
             bool highCq,
+            bool hasPtc,
             Aarch32Mode mode)
         {
-            HasPtc = Ptc.State != PtcState.Disabled;
             Memory = memory;
             CountTable = countTable;
             FunctionTable = funcTable;
             Stubs = stubs;
             EntryAddress = entryAddress;
             HighCq = highCq;
+            HasPtc = hasPtc;
             Mode = mode;
 
             _labels = new Dictionary<ulong, Operand>();
diff --git a/ARMeilleure/Translation/PTC/IPtcLoadState.cs b/ARMeilleure/Translation/PTC/IPtcLoadState.cs
new file mode 100644
index 000000000..1b11ac0b5
--- /dev/null
+++ b/ARMeilleure/Translation/PTC/IPtcLoadState.cs
@@ -0,0 +1,10 @@
+using System;
+
+namespace ARMeilleure.Translation.PTC
+{
+    public interface IPtcLoadState
+    {
+        event Action<PtcLoadingState, int, int> PtcStateChanged;
+        void Continue();
+    }
+}
\ No newline at end of file
diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
index c2d358c1e..f99d6e516 100644
--- a/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -22,7 +22,7 @@ using static ARMeilleure.Translation.PTC.PtcFormatter;
 
 namespace ARMeilleure.Translation.PTC
 {
-    public static class Ptc
+    class Ptc : IPtcLoadState
     {
         private const string OuterHeaderMagicString = "PTCohd\0\0";
         private const string InnerHeaderMagicString = "PTCihd\0\0";
@@ -35,45 +35,49 @@ namespace ARMeilleure.Translation.PTC
         private const string TitleIdTextDefault = "0000000000000000";
         private const string DisplayVersionDefault = "0";
 
-        internal static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
-        internal static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
-        internal static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
+        public static readonly Symbol PageTableSymbol = new(SymbolType.Special, 1);
+        public static readonly Symbol CountTableSymbol = new(SymbolType.Special, 2);
+        public static readonly Symbol DispatchStubSymbol = new(SymbolType.Special, 3);
 
         private const byte FillingByte = 0x00;
         private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
 
+        public PtcProfiler Profiler { get; }
+
         // Carriers.
-        private static MemoryStream _infosStream;
-        private static List<byte[]> _codesList;
-        private static MemoryStream _relocsStream;
-        private static MemoryStream _unwindInfosStream;
+        private MemoryStream _infosStream;
+        private List<byte[]> _codesList;
+        private MemoryStream _relocsStream;
+        private MemoryStream _unwindInfosStream;
 
-        private static readonly ulong _outerHeaderMagic;
-        private static readonly ulong _innerHeaderMagic;
+        private readonly ulong _outerHeaderMagic;
+        private readonly ulong _innerHeaderMagic;
 
-        private static readonly ManualResetEvent _waitEvent;
+        private readonly ManualResetEvent _waitEvent;
 
-        private static readonly object _lock;
+        private readonly object _lock;
 
-        private static bool _disposed;
+        private bool _disposed;
 
-        internal static string TitleIdText { get; private set; }
-        internal static string DisplayVersion { get; private set; }
+        public string TitleIdText { get; private set; }
+        public string DisplayVersion { get; private set; }
 
-        private static MemoryManagerMode _memoryMode;
+        private MemoryManagerType _memoryMode;
 
-        internal static string CachePathActual { get; private set; }
-        internal static string CachePathBackup { get; private set; }
+        public string CachePathActual { get; private set; }
+        public string CachePathBackup { get; private set; }
 
-        internal static PtcState State { get; private set; }
+        public PtcState State { get; private set; }
 
         // Progress reporting helpers.
-        private static volatile int _translateCount;
-        private static volatile int _translateTotalCount;
-        public static event Action<PtcLoadingState, int, int> PtcStateChanged;
+        private volatile int _translateCount;
+        private volatile int _translateTotalCount;
+        public event Action<PtcLoadingState, int, int> PtcStateChanged;
 
-        static Ptc()
+        public Ptc()
         {
+            Profiler = new PtcProfiler(this);
+
             InitializeCarriers();
 
             _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
@@ -94,12 +98,12 @@ namespace ARMeilleure.Translation.PTC
             Disable();
         }
 
-        public static void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerMode memoryMode)
+        public void Initialize(string titleIdText, string displayVersion, bool enabled, MemoryManagerType memoryMode)
         {
             Wait();
 
-            PtcProfiler.Wait();
-            PtcProfiler.ClearEntries();
+            Profiler.Wait();
+            Profiler.ClearEntries();
 
             Logger.Info?.Print(LogClass.Ptc, $"Initializing Profiled Persistent Translation Cache (enabled: {enabled}).");
 
@@ -137,12 +141,12 @@ namespace ARMeilleure.Translation.PTC
             CachePathBackup = Path.Combine(workPathBackup, DisplayVersion);
 
             PreLoad();
-            PtcProfiler.PreLoad();
+            Profiler.PreLoad();
 
             Enable();
         }
 
-        private static void InitializeCarriers()
+        private void InitializeCarriers()
         {
             _infosStream = new MemoryStream();
             _codesList = new List<byte[]>();
@@ -150,7 +154,7 @@ namespace ARMeilleure.Translation.PTC
             _unwindInfosStream = new MemoryStream();
         }
 
-        private static void DisposeCarriers()
+        private void DisposeCarriers()
         {
             _infosStream.Dispose();
             _codesList.Clear();
@@ -158,12 +162,12 @@ namespace ARMeilleure.Translation.PTC
             _unwindInfosStream.Dispose();
         }
 
-        private static bool AreCarriersEmpty()
+        private bool AreCarriersEmpty()
         {
             return _infosStream.Length == 0L && _codesList.Count == 0 && _relocsStream.Length == 0L && _unwindInfosStream.Length == 0L;
         }
 
-        private static void ResetCarriersIfNeeded()
+        private void ResetCarriersIfNeeded()
         {
             if (AreCarriersEmpty())
             {
@@ -175,7 +179,7 @@ namespace ARMeilleure.Translation.PTC
             InitializeCarriers();
         }
 
-        private static void PreLoad()
+        private void PreLoad()
         {
             string fileNameActual = string.Concat(CachePathActual, ".cache");
             string fileNameBackup = string.Concat(CachePathBackup, ".cache");
@@ -199,7 +203,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        private static unsafe bool Load(string fileName, bool isBackup)
+        private unsafe bool Load(string fileName, bool isBackup)
         {
             using (FileStream compressedStream = new(fileName, FileMode.Open))
             using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true))
@@ -376,12 +380,12 @@ namespace ARMeilleure.Translation.PTC
             return true;
         }
 
-        private static void InvalidateCompressedStream(FileStream compressedStream)
+        private void InvalidateCompressedStream(FileStream compressedStream)
         {
             compressedStream.SetLength(0L);
         }
 
-        private static void PreSave()
+        private void PreSave()
         {
             _waitEvent.Reset();
 
@@ -409,7 +413,7 @@ namespace ARMeilleure.Translation.PTC
             _waitEvent.Set();
         }
 
-        private static unsafe void Save(string fileName)
+        private unsafe void Save(string fileName)
         {
             int translatedFuncsCount;
 
@@ -517,7 +521,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static void LoadTranslations(Translator translator)
+        public void LoadTranslations(Translator translator)
         {
             if (AreCarriersEmpty())
             {
@@ -550,7 +554,7 @@ namespace ARMeilleure.Translation.PTC
 
                     bool isEntryChanged = infoEntry.Hash != ComputeHash(translator.Memory, infoEntry.Address, infoEntry.GuestSize);
 
-                    if (isEntryChanged || (!infoEntry.HighCq && PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq))
+                    if (isEntryChanged || (!infoEntry.HighCq && Profiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq))
                     {
                         infoEntry.Stubbed = true;
                         infoEntry.CodeLength = 0;
@@ -601,38 +605,38 @@ namespace ARMeilleure.Translation.PTC
             Logger.Info?.Print(LogClass.Ptc, $"{translator.Functions.Count} translated functions loaded");
         }
 
-        private static int GetEntriesCount()
+        private int GetEntriesCount()
         {
             return _codesList.Count;
         }
 
         [Conditional("DEBUG")]
-        private static void SkipCode(int index, int codeLength)
+        private void SkipCode(int index, int codeLength)
         {
             Debug.Assert(_codesList[index].Length == 0);
             Debug.Assert(codeLength == 0);
         }
 
-        private static void SkipReloc(int relocEntriesCount)
+        private void SkipReloc(int relocEntriesCount)
         {
             _relocsStream.Seek(relocEntriesCount * RelocEntry.Stride, SeekOrigin.Current);
         }
 
-        private static void SkipUnwindInfo(BinaryReader unwindInfosReader)
+        private void SkipUnwindInfo(BinaryReader unwindInfosReader)
         {
             int pushEntriesLength = unwindInfosReader.ReadInt32();
 
             _unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current);
         }
 
-        private static byte[] ReadCode(int index, int codeLength)
+        private byte[] ReadCode(int index, int codeLength)
         {
             Debug.Assert(_codesList[index].Length == codeLength);
 
             return _codesList[index];
         }
 
-        private static RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount)
+        private RelocEntry[] GetRelocEntries(BinaryReader relocsReader, int relocEntriesCount)
         {
             RelocEntry[] relocEntries = new RelocEntry[relocEntriesCount];
 
@@ -648,7 +652,7 @@ namespace ARMeilleure.Translation.PTC
             return relocEntries;
         }
 
-        private static void PatchCode(Translator translator, Span<byte> code, RelocEntry[] relocEntries, out Counter<uint> callCounter)
+        private void PatchCode(Translator translator, Span<byte> code, RelocEntry[] relocEntries, out Counter<uint> callCounter)
         {
             callCounter = null;
 
@@ -702,7 +706,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        private static UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader)
+        private UnwindInfo ReadUnwindInfo(BinaryReader unwindInfosReader)
         {
             int pushEntriesLength = unwindInfosReader.ReadInt32();
 
@@ -723,7 +727,7 @@ namespace ARMeilleure.Translation.PTC
             return new UnwindInfo(pushEntries, prologueSize);
         }
 
-        private static TranslatedFunction FastTranslate(
+        private TranslatedFunction FastTranslate(
             byte[] code,
             Counter<uint> callCounter,
             ulong guestSize,
@@ -736,19 +740,19 @@ namespace ARMeilleure.Translation.PTC
             return new TranslatedFunction(gFunc, callCounter, guestSize, highCq);
         }
 
-        private static void UpdateInfo(InfoEntry infoEntry)
+        private void UpdateInfo(InfoEntry infoEntry)
         {
             _infosStream.Seek(-Unsafe.SizeOf<InfoEntry>(), SeekOrigin.Current);
 
             SerializeStructure(_infosStream, infoEntry);
         }
 
-        private static void StubCode(int index)
+        private void StubCode(int index)
         {
             _codesList[index] = Array.Empty<byte>();
         }
 
-        private static void StubReloc(int relocEntriesCount)
+        private void StubReloc(int relocEntriesCount)
         {
             for (int i = 0; i < relocEntriesCount * RelocEntry.Stride; i++)
             {
@@ -756,7 +760,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        private static void StubUnwindInfo(BinaryReader unwindInfosReader)
+        private void StubUnwindInfo(BinaryReader unwindInfosReader)
         {
             int pushEntriesLength = unwindInfosReader.ReadInt32();
 
@@ -766,9 +770,9 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static void MakeAndSaveTranslations(Translator translator)
+        public void MakeAndSaveTranslations(Translator translator)
         {
-            var profiledFuncsToTranslate = PtcProfiler.GetProfiledFuncsToTranslate(translator.Functions);
+            var profiledFuncsToTranslate = Profiler.GetProfiledFuncsToTranslate(translator.Functions);
 
             _translateCount = 0;
             _translateTotalCount = profiledFuncsToTranslate.Count;
@@ -811,7 +815,7 @@ namespace ARMeilleure.Translation.PTC
                 {
                     ulong address = item.address;
 
-                    Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address));
+                    Debug.Assert(Profiler.IsAddressInStaticCodeRange(address));
 
                     TranslatedFunction func = translator.Translate(address, item.funcProfile.Mode, item.funcProfile.HighCq);
 
@@ -861,7 +865,7 @@ namespace ARMeilleure.Translation.PTC
             preSaveThread.Start();
         }
 
-        private static void ReportProgress(object state)
+        private void ReportProgress(object state)
         {
             const int refreshRate = 50; // ms.
 
@@ -882,12 +886,12 @@ namespace ARMeilleure.Translation.PTC
             while (!endEvent.WaitOne(refreshRate));
         }
 
-        internal static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
+        public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
         {
             return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
         }
 
-        internal static void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc)
+        public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc)
         {
             lock (_lock)
             {
@@ -936,12 +940,12 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        private static void WriteCode(ReadOnlySpan<byte> code)
+        private void WriteCode(ReadOnlySpan<byte> code)
         {
             _codesList.Add(code.ToArray());
         }
 
-        internal static bool GetEndianness()
+        public static bool GetEndianness()
         {
             return BitConverter.IsLittleEndian;
         }
@@ -955,7 +959,7 @@ namespace ARMeilleure.Translation.PTC
                 (uint)HardwareCapabilities.FeatureInfo7Ecx);
         }
 
-        private static byte GetMemoryManagerMode()
+        private byte GetMemoryManagerMode()
         {
             return (byte)_memoryMode;
         }
@@ -1050,12 +1054,12 @@ namespace ARMeilleure.Translation.PTC
             public int RelocEntriesCount;
         }
 
-        private static void Enable()
+        private void Enable()
         {
             State = PtcState.Enabled;
         }
 
-        public static void Continue()
+        public void Continue()
         {
             if (State == PtcState.Enabled)
             {
@@ -1063,7 +1067,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        public static void Close()
+        public void Close()
         {
             if (State == PtcState.Enabled ||
                 State == PtcState.Continuing)
@@ -1072,17 +1076,17 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static void Disable()
+        public void Disable()
         {
             State = PtcState.Disabled;
         }
 
-        private static void Wait()
+        private void Wait()
         {
             _waitEvent.WaitOne();
         }
 
-        public static void Dispose()
+        public void Dispose()
         {
             if (!_disposed)
             {
diff --git a/ARMeilleure/Translation/PTC/PtcProfiler.cs b/ARMeilleure/Translation/PTC/PtcProfiler.cs
index bb70da8d0..0d5546283 100644
--- a/ARMeilleure/Translation/PTC/PtcProfiler.cs
+++ b/ARMeilleure/Translation/PTC/PtcProfiler.cs
@@ -16,7 +16,7 @@ using static ARMeilleure.Translation.PTC.PtcFormatter;
 
 namespace ARMeilleure.Translation.PTC
 {
-    public static class PtcProfiler
+    class PtcProfiler
     {
         private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
 
@@ -26,27 +26,31 @@ namespace ARMeilleure.Translation.PTC
 
         private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
 
-        private static readonly System.Timers.Timer _timer;
+        private readonly Ptc _ptc;
 
-        private static readonly ulong _outerHeaderMagic;
+        private readonly System.Timers.Timer _timer;
 
-        private static readonly ManualResetEvent _waitEvent;
+        private readonly ulong _outerHeaderMagic;
 
-        private static readonly object _lock;
+        private readonly ManualResetEvent _waitEvent;
 
-        private static bool _disposed;
+        private readonly object _lock;
 
-        private static Hash128 _lastHash;
+        private bool _disposed;
 
-        internal static Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
+        private Hash128 _lastHash;
 
-        internal static bool Enabled { get; private set; }
+        public Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
 
-        public static ulong StaticCodeStart { internal get; set; }
-        public static ulong StaticCodeSize  { internal get; set; }
+        public bool Enabled { get; private set; }
 
-        static PtcProfiler()
+        public ulong StaticCodeStart { get; set; }
+        public ulong StaticCodeSize  { get; set; }
+
+        public PtcProfiler(Ptc ptc)
         {
+            _ptc = ptc;
+
             _timer = new System.Timers.Timer((double)SaveInterval * 1000d);
             _timer.Elapsed += PreSave;
 
@@ -63,7 +67,7 @@ namespace ARMeilleure.Translation.PTC
             Enabled = false;
         }
 
-        internal static void AddEntry(ulong address, ExecutionMode mode, bool highCq)
+        public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
         {
             if (IsAddressInStaticCodeRange(address))
             {
@@ -76,7 +80,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static void UpdateEntry(ulong address, ExecutionMode mode, bool highCq)
+        public void UpdateEntry(ulong address, ExecutionMode mode, bool highCq)
         {
             if (IsAddressInStaticCodeRange(address))
             {
@@ -91,12 +95,12 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static bool IsAddressInStaticCodeRange(ulong address)
+        public bool IsAddressInStaticCodeRange(ulong address)
         {
             return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
         }
 
-        internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
+        public ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
         {
             var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
 
@@ -111,18 +115,18 @@ namespace ARMeilleure.Translation.PTC
             return profiledFuncsToTranslate;
         }
 
-        internal static void ClearEntries()
+        public void ClearEntries()
         {
             ProfiledFuncs.Clear();
             ProfiledFuncs.TrimExcess();
         }
 
-        internal static void PreLoad()
+        public void PreLoad()
         {
             _lastHash = default;
 
-            string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
-            string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info");
+            string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
+            string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
 
             FileInfo fileInfoActual = new FileInfo(fileNameActual);
             FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
@@ -143,7 +147,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        private static bool Load(string fileName, bool isBackup)
+        private bool Load(string fileName, bool isBackup)
         {
             using (FileStream compressedStream = new(fileName, FileMode.Open))
             using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true))
@@ -228,22 +232,22 @@ namespace ARMeilleure.Translation.PTC
             return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
         }
 
-        private static ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
+        private ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
         {
             return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position);
         }
 
-        private static void InvalidateCompressedStream(FileStream compressedStream)
+        private void InvalidateCompressedStream(FileStream compressedStream)
         {
             compressedStream.SetLength(0L);
         }
 
-        private static void PreSave(object source, System.Timers.ElapsedEventArgs e)
+        private void PreSave(object source, System.Timers.ElapsedEventArgs e)
         {
             _waitEvent.Reset();
 
-            string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
-            string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info");
+            string fileNameActual = string.Concat(_ptc.CachePathActual, ".info");
+            string fileNameBackup = string.Concat(_ptc.CachePathBackup, ".info");
 
             FileInfo fileInfoActual = new FileInfo(fileNameActual);
 
@@ -257,7 +261,7 @@ namespace ARMeilleure.Translation.PTC
             _waitEvent.Set();
         }
 
-        private static void Save(string fileName)
+        private void Save(string fileName)
         {
             int profiledFuncsCount;
 
@@ -329,7 +333,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        private static void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
+        private void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
         {
             SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
         }
@@ -361,7 +365,7 @@ namespace ARMeilleure.Translation.PTC
         }
 
         [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)]
-        internal struct FuncProfile
+        public struct FuncProfile
         {
             public ExecutionMode Mode;
             public bool HighCq;
@@ -373,10 +377,10 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static void Start()
+        public void Start()
         {
-            if (Ptc.State == PtcState.Enabled ||
-                Ptc.State == PtcState.Continuing)
+            if (_ptc.State == PtcState.Enabled ||
+                _ptc.State == PtcState.Continuing)
             {
                 Enabled = true;
 
@@ -384,7 +388,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        public static void Stop()
+        public void Stop()
         {
             Enabled = false;
 
@@ -394,12 +398,12 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static void Wait()
+        public void Wait()
         {
             _waitEvent.WaitOne();
         }
 
-        public static void Dispose()
+        public void Dispose()
         {
             if (!_disposed)
             {
diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs
index 2edbe4011..77ccdaeab 100644
--- a/ARMeilleure/Translation/Translator.cs
+++ b/ARMeilleure/Translation/Translator.cs
@@ -44,6 +44,8 @@ namespace ARMeilleure.Translation
         private readonly IJitMemoryAllocator _allocator;
         private readonly ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>> _oldFuncs;
 
+        private readonly Ptc _ptc;
+
         internal TranslatorCache<TranslatedFunction> Functions { get; }
         internal AddressTable<ulong> FunctionTable { get; }
         internal EntryTable<uint> CountTable { get; }
@@ -63,6 +65,8 @@ namespace ARMeilleure.Translation
 
             _oldFuncs = new ConcurrentQueue<KeyValuePair<ulong, TranslatedFunction>>();
 
+            _ptc = new Ptc();
+
             Queue = new TranslatorQueue();
 
             JitCache.Initialize(allocator);
@@ -80,22 +84,37 @@ namespace ARMeilleure.Translation
             }
         }
 
+        public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
+        {
+            _ptc.Initialize(titleIdText, displayVersion, enabled, Memory.Type);
+            return _ptc;
+        }
+
+        public void PrepareCodeRange(ulong address, ulong size)
+        {
+            if (_ptc.Profiler.StaticCodeSize == 0)
+            {
+                _ptc.Profiler.StaticCodeStart = address;
+                _ptc.Profiler.StaticCodeSize = size;
+            }
+        }
+
         public void Execute(State.ExecutionContext context, ulong address)
         {
             if (Interlocked.Increment(ref _threadCount) == 1)
             {
                 IsReadyForTranslation.WaitOne();
 
-                if (Ptc.State == PtcState.Enabled)
+                if (_ptc.State == PtcState.Enabled)
                 {
                     Debug.Assert(Functions.Count == 0);
-                    Ptc.LoadTranslations(this);
-                    Ptc.MakeAndSaveTranslations(this);
+                    _ptc.LoadTranslations(this);
+                    _ptc.MakeAndSaveTranslations(this);
                 }
 
-                PtcProfiler.Start();
+                _ptc.Profiler.Start();
 
-                Ptc.Disable();
+                _ptc.Disable();
 
                 // Simple heuristic, should be user configurable in future. (1 for 4 core/ht or less, 2 for 6 core + ht
                 // etc). All threads are normal priority except from the last, which just fills as much of the last core
@@ -148,6 +167,12 @@ namespace ARMeilleure.Translation
                 Stubs.Dispose();
                 FunctionTable.Dispose();
                 CountTable.Dispose();
+
+                _ptc.Close();
+                _ptc.Profiler.Stop();
+
+                _ptc.Dispose();
+                _ptc.Profiler.Dispose();
             }
         }
 
@@ -189,9 +214,9 @@ namespace ARMeilleure.Translation
                     func = oldFunc;
                 }
 
-                if (PtcProfiler.Enabled)
+                if (_ptc.Profiler.Enabled)
                 {
-                    PtcProfiler.AddEntry(address, mode, highCq: false);
+                    _ptc.Profiler.AddEntry(address, mode, highCq: false);
                 }
 
                 RegisterFunction(address, func);
@@ -217,6 +242,7 @@ namespace ARMeilleure.Translation
                 Stubs,
                 address,
                 highCq,
+                _ptc.State != PtcState.Disabled,
                 mode: Aarch32Mode.User);
 
             Logger.StartPass(PassName.Decoding);
@@ -262,7 +288,7 @@ namespace ARMeilleure.Translation
             {
                 Hash128 hash = Ptc.ComputeHash(Memory, address, funcSize);
 
-                Ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
+                _ptc.WriteCompiledFunction(address, funcSize, hash, highCq, compiledFunc);
             }
 
             GuestFunction func = compiledFunc.Map<GuestFunction>();
@@ -284,9 +310,9 @@ namespace ARMeilleure.Translation
                     return func;
                 });
 
-                if (PtcProfiler.Enabled)
+                if (_ptc.Profiler.Enabled)
                 {
-                    PtcProfiler.UpdateEntry(request.Address, request.Mode, highCq: true);
+                    _ptc.Profiler.UpdateEntry(request.Address, request.Mode, highCq: true);
                 }
 
                 RegisterFunction(request.Address, func);
diff --git a/Ryujinx.Ava/AppHost.cs b/Ryujinx.Ava/AppHost.cs
index 0baa94c3b..2cf53ef69 100644
--- a/Ryujinx.Ava/AppHost.cs
+++ b/Ryujinx.Ava/AppHost.cs
@@ -1,5 +1,4 @@
 using ARMeilleure.Translation;
-using ARMeilleure.Translation.PTC;
 using Avalonia.Input;
 using Avalonia.Threading;
 using LibHac.Tools.FsSystem;
@@ -280,7 +279,7 @@ namespace Ryujinx.Ava
                 _parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
             });
 
-            _parent.ViewModel.HandleShaderProgress(Device);
+            _parent.ViewModel.SetUiProgressHandlers(Device);
 
             Renderer.SizeChanged += Window_SizeChanged;
 
@@ -357,8 +356,6 @@ namespace Ryujinx.Ava
 
             DisplaySleep.Restore();
 
-            Ptc.Close();
-            PtcProfiler.Stop();
             NpadManager.Dispose();
             TouchScreenManager.Dispose();
             Device.Dispose();
@@ -949,7 +946,7 @@ namespace Ryujinx.Ava
 
                     if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _parent.WindowState != WindowState.FullScreen)
                     {
-                        Ptc.Continue();
+                        Device.Application.DiskCacheLoadState?.Cancel();
                     }
                 });
             }
diff --git a/Ryujinx.Ava/Program.cs b/Ryujinx.Ava/Program.cs
index 142d7820b..836801c88 100644
--- a/Ryujinx.Ava/Program.cs
+++ b/Ryujinx.Ava/Program.cs
@@ -1,4 +1,3 @@
-using ARMeilleure.Translation.PTC;
 using Avalonia;
 using Avalonia.Threading;
 using Ryujinx.Ava.UI.Windows;
@@ -197,9 +196,6 @@ namespace Ryujinx.Ava
 
         private static void ProcessUnhandledException(Exception ex, bool isTerminating)
         {
-            Ptc.Close();
-            PtcProfiler.Stop();
-
             string message = $"Unhandled exception caught: {ex}";
 
             Logger.Error?.PrintMsg(LogClass.Application, message);
@@ -219,9 +215,6 @@ namespace Ryujinx.Ava
         {
             DiscordIntegrationModule.Exit();
 
-            Ptc.Dispose();
-            PtcProfiler.Dispose();
-
             Logger.Shutdown();
         }
     }
diff --git a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
index 878af3f81..953f8562c 100644
--- a/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
+++ b/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
@@ -1,4 +1,3 @@
-using ARMeilleure.Translation.PTC;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
@@ -18,6 +17,7 @@ using Ryujinx.Ava.UI.Windows;
 using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
 using Ryujinx.HLE;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.HOS;
@@ -107,9 +107,6 @@ namespace Ryujinx.Ava.UI.ViewModels
         {
             ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
             ApplicationLibrary.ApplicationAdded += ApplicationLibrary_ApplicationAdded;
-
-            Ptc.PtcStateChanged -= ProgressHandler;
-            Ptc.PtcStateChanged += ProgressHandler;
         }
 
         public string SearchText
@@ -436,7 +433,7 @@ namespace Ryujinx.Ava.UI.ViewModels
                 OnPropertyChanged();
             }
         }
-        
+
         public bool ShowMenuAndStatusBar
         {
             get => _showMenuAndStatusBar;
@@ -745,8 +742,14 @@ namespace Ryujinx.Ava.UI.ViewModels
             }
         }
 
-        public void HandleShaderProgress(Switch emulationContext)
+        public void SetUiProgressHandlers(Switch emulationContext)
         {
+            if (emulationContext.Application.DiskCacheLoadState != null)
+            {
+                emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
+                emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
+            }
+
             emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
             emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
         }
@@ -1033,16 +1036,16 @@ namespace Ryujinx.Ava.UI.ViewModels
 
                 switch (state)
                 {
-                    case PtcLoadingState ptcState:
+                    case LoadState ptcState:
                         CacheLoadStatus = $"{current} / {total}";
                         switch (ptcState)
                         {
-                            case PtcLoadingState.Start:
-                            case PtcLoadingState.Loading:
+                            case LoadState.Unloaded:
+                            case LoadState.Loading:
                                 LoadHeading            = LocaleManager.Instance[LocaleKeys.CompilingPPTC];
                                 IsLoadingIndeterminate = false;
                                 break;
-                            case PtcLoadingState.Loaded:
+                            case LoadState.Loaded:
                                 LoadHeading            = string.Format(LocaleManager.Instance[LocaleKeys.LoadingHeading], TitleName);
                                 IsLoadingIndeterminate = true;
                                 CacheLoadStatus        = "";
@@ -1166,7 +1169,7 @@ namespace Ryujinx.Ava.UI.ViewModels
                 DirectoryInfo backupDir = new(Path.Combine(AppDataManager.GamesDirPath, selection.TitleId, "cache", "cpu", "1"));
 
                 // FIXME: Found a way to reproduce the bold effect on the title name (fork?).
-                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], 
+                UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning],
                                                                                        string.Format(LocaleManager.Instance[LocaleKeys.DialogPPTCDeletionMessage], selection.TitleName),
                                                                                        LocaleManager.Instance[LocaleKeys.InputDialogYes],
                                                                                        LocaleManager.Instance[LocaleKeys.InputDialogNo],
diff --git a/Ryujinx.Cpu/ICpuContext.cs b/Ryujinx.Cpu/ICpuContext.cs
index 4a73a8338..80916d1ca 100644
--- a/Ryujinx.Cpu/ICpuContext.cs
+++ b/Ryujinx.Cpu/ICpuContext.cs
@@ -35,5 +35,27 @@ namespace Ryujinx.Cpu
         /// <param name="address">Address of the region to be invalidated</param>
         /// <param name="size">Size of the region to be invalidated</param>
         void InvalidateCacheRegion(ulong address, ulong size);
+
+        /// <summary>
+        /// Loads cached code from disk for a given application.
+        /// </summary>
+        /// <remarks>
+        /// If the execution engine is recompiling guest code, this can be used to load cached code from disk.
+        /// </remarks>
+        /// <param name="titleIdText">Title ID of the application in padded hex form</param>
+        /// <param name="displayVersion">Version of the application</param>
+        /// <param name="enabled">True if the cache should be loaded from disk if it exists, false otherwise</param>
+        /// <returns>Disk cache load progress reporter and manager</returns>
+        IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled);
+
+        /// <summary>
+        /// Indicates that code has been loaded into guest memory, and that it might be executed in the future.
+        /// </summary>
+        /// <remarks>
+        /// Some execution engines might use this information to cache recompiled code on disk or to ensure it can be executed.
+        /// </remarks>
+        /// <param name="address">CPU virtual address where the code starts</param>
+        /// <param name="size">Size of the code range in bytes</param>
+        void PrepareCodeRange(ulong address, ulong size);
     }
 }
diff --git a/Ryujinx.Cpu/IDiskCacheState.cs b/Ryujinx.Cpu/IDiskCacheState.cs
new file mode 100644
index 000000000..61bbdf924
--- /dev/null
+++ b/Ryujinx.Cpu/IDiskCacheState.cs
@@ -0,0 +1,20 @@
+using System;
+
+namespace Ryujinx.Cpu
+{
+    /// <summary>
+    /// Disk cache load state report and management interface.
+    /// </summary>
+    public interface IDiskCacheLoadState
+    {
+        /// <summary>
+        /// Event used to report the cache load progress.
+        /// </summary>
+        event Action<LoadState, int, int> StateChanged;
+
+        /// <summary>
+        /// Cancels the disk cache load process.
+        /// </summary>
+        void Cancel();
+    }
+}
diff --git a/Ryujinx.Cpu/Jit/JitCpuContext.cs b/Ryujinx.Cpu/Jit/JitCpuContext.cs
index d6892ea75..02465a0b3 100644
--- a/Ryujinx.Cpu/Jit/JitCpuContext.cs
+++ b/Ryujinx.Cpu/Jit/JitCpuContext.cs
@@ -37,5 +37,17 @@ namespace Ryujinx.Cpu.Jit
         {
             _translator.InvalidateJitCacheRegion(address, size);
         }
+
+        /// <inheritdoc/>
+        public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
+        {
+            return new JitDiskCacheLoadState(_translator.LoadDiskCache(titleIdText, displayVersion, enabled));
+        }
+
+        /// <inheritdoc/>
+        public void PrepareCodeRange(ulong address, ulong size)
+        {
+            _translator.PrepareCodeRange(address, size);
+        }
     }
 }
diff --git a/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs b/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs
new file mode 100644
index 000000000..7a4b670b3
--- /dev/null
+++ b/Ryujinx.Cpu/Jit/JitDiskCacheLoadState.cs
@@ -0,0 +1,38 @@
+using ARMeilleure.Translation.PTC;
+using System;
+
+namespace Ryujinx.Cpu.Jit
+{
+    public class JitDiskCacheLoadState : IDiskCacheLoadState
+    {
+        /// <inheritdoc/>
+        public event Action<LoadState, int, int> StateChanged;
+
+        private readonly IPtcLoadState _loadState;
+
+        public JitDiskCacheLoadState(IPtcLoadState loadState)
+        {
+            loadState.PtcStateChanged += LoadStateChanged;
+            _loadState = loadState;
+        }
+
+        private void LoadStateChanged(PtcLoadingState newState, int current, int total)
+        {
+            LoadState state = newState switch
+            {
+                PtcLoadingState.Start => LoadState.Unloaded,
+                PtcLoadingState.Loading => LoadState.Loading,
+                PtcLoadingState.Loaded => LoadState.Loaded,
+                _ => throw new ArgumentException($"Invalid load state \"{newState}\".")
+            };
+
+            StateChanged?.Invoke(state, current, total);
+        }
+
+        /// <inheritdoc/>
+        public void Cancel()
+        {
+            _loadState.Continue();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Cpu/LoadState.cs b/Ryujinx.Cpu/LoadState.cs
new file mode 100644
index 000000000..1f2e1ae8c
--- /dev/null
+++ b/Ryujinx.Cpu/LoadState.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Cpu
+{
+    /// <summary>
+    /// Load state.
+    /// </summary>
+    public enum LoadState
+    {
+        Unloaded,
+        Loading,
+        Loaded
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
index 61fcd0c35..06281b497 100644
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs
@@ -1,4 +1,3 @@
-using ARMeilleure.Translation.PTC;
 using LibHac;
 using LibHac.Account;
 using LibHac.Common;
@@ -14,8 +13,8 @@ using LibHac.Tools.FsSystem;
 using LibHac.Tools.FsSystem.NcaUtils;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
 using Ryujinx.HLE.FileSystem;
-using Ryujinx.HLE.HOS.Kernel.Process;
 using Ryujinx.HLE.Loaders.Executables;
 using Ryujinx.Memory;
 using System;
@@ -67,6 +66,8 @@ namespace Ryujinx.HLE.HOS
 
         public string TitleIdText => TitleId.ToString("x16");
 
+        public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
+
         public ApplicationLoader(Switch device)
         {
             _device = device;
@@ -94,7 +95,7 @@ namespace Ryujinx.HLE.HOS
                 EnsureSaveData(new ApplicationId(TitleId));
             }
 
-            LoadExeFs(codeFs, metaData);
+            LoadExeFs(codeFs, string.Empty, metaData);
         }
 
         public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
@@ -302,12 +303,6 @@ namespace Ryujinx.HLE.HOS
 
         public void LoadServiceNca(string ncaFile)
         {
-            // Disable PPTC here as it does not support multiple processes running.
-            // TODO: This should be eventually removed and it should stop using global state and
-            // instead manage the cache per process.
-            Ptc.Close();
-            PtcProfiler.Stop();
-
             FileStream file = new FileStream(ncaFile, FileMode.Open, FileAccess.Read);
             Nca mainNca = new Nca(_device.Configuration.VirtualFileSystem.KeySet, file.AsStorage(false));
 
@@ -369,16 +364,12 @@ namespace Ryujinx.HLE.HOS
             // Collect the nsos, ignoring ones that aren't used.
             NsoExecutable[] programs = nsos.Where(x => x != null).ToArray();
 
-            MemoryManagerMode memoryManagerMode = _device.Configuration.MemoryManagerMode;
-
-            if (!MemoryBlock.SupportsFlags(MemoryAllocationFlags.ViewCompatible))
-            {
-                memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
-            }
+            string displayVersion = _device.System.ContentManager.GetCurrentFirmwareVersion().VersionString;
+            bool usePtc = _device.System.EnablePtc;
 
             metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
-            ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: false);
-            ProgramLoader.LoadNsos(_device.System.KernelContext, out _, metaData, programInfo, executables: programs);
+            ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit: false);
+            ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
 
             string titleIdText = npdm.Aci.Value.ProgramId.Value.ToString("x16");
             bool titleIs64Bit = (npdm.Meta.Value.Flags & 1) != 0;
@@ -477,9 +468,11 @@ namespace Ryujinx.HLE.HOS
                 _device.Configuration.VirtualFileSystem.ModLoader.GetModsBasePath(),
                 _device.Configuration.VirtualFileSystem.ModLoader.GetSdModsBasePath());
 
+            string displayVersion = string.Empty;
+
             if (controlNca != null)
             {
-                ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref _displayVersion);
+                ReadControlData(_device, controlNca, ref _controlData, ref _titleName, ref displayVersion);
             }
             else
             {
@@ -493,9 +486,11 @@ namespace Ryujinx.HLE.HOS
                 string dummyTitleName = "";
                 BlitStruct<ApplicationControlProperty> dummyControl = new BlitStruct<ApplicationControlProperty>(1);
 
-                ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref _displayVersion);
+                ReadControlData(_device, updateProgram0ControlNca, ref dummyControl, ref dummyTitleName, ref displayVersion);
             }
 
+            _displayVersion = displayVersion;
+
             if (dataStorage == null)
             {
                 Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
@@ -515,7 +510,7 @@ namespace Ryujinx.HLE.HOS
                 EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
             }
 
-            LoadExeFs(codeFs, metaData);
+            LoadExeFs(codeFs, displayVersion, metaData);
 
             Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
         }
@@ -584,7 +579,7 @@ namespace Ryujinx.HLE.HOS
             }
         }
 
-        private void LoadExeFs(IFileSystem codeFs, MetaLoader metaData = null, bool isHomebrew = false)
+        private void LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
         {
             if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
             {
@@ -649,23 +644,23 @@ namespace Ryujinx.HLE.HOS
                 memoryManagerMode = MemoryManagerMode.SoftwarePageTable;
             }
 
-            Ptc.Initialize(TitleIdText, DisplayVersion, usePtc, memoryManagerMode);
-
             // We allow it for nx-hbloader because it can be used to launch homebrew.
             bool allowCodeMemoryForJit = TitleId == 0x010000000000100DUL || isHomebrew;
 
             metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
-            ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit);
-            ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: programs);
+            ProgramInfo programInfo = new ProgramInfo(in npdm, displayVersion, usePtc, allowCodeMemoryForJit);
+            ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: programs);
 
-            _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
+            DiskCacheLoadState = result.DiskCacheLoadState;
+
+            _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
         }
 
         public void LoadProgram(string filePath)
         {
             MetaLoader metaData = GetDefaultNpdm();
             metaData.GetNpdm(out Npdm npdm).ThrowIfFailure();
-            ProgramInfo programInfo = new ProgramInfo(in npdm, allowCodeMemoryForJit: true);
+            ProgramInfo programInfo = new ProgramInfo(in npdm, string.Empty, diskCacheEnabled: false, allowCodeMemoryForJit: true);
 
             bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
 
@@ -761,9 +756,11 @@ namespace Ryujinx.HLE.HOS
             Graphics.Gpu.GraphicsConfig.TitleId = null;
             _device.Gpu.HostInitalized.Set();
 
-            ProgramLoader.LoadNsos(_device.System.KernelContext, out ProcessTamperInfo tamperInfo, metaData, programInfo, executables: executable);
+            ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
 
-            _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, tamperInfo, _device.TamperMachine);
+            DiskCacheLoadState = result.DiskCacheLoadState;
+
+            _device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
         }
 
         private MetaLoader GetDefaultNpdm()
diff --git a/Ryujinx.HLE/HOS/ArmProcessContext.cs b/Ryujinx.HLE/HOS/ArmProcessContext.cs
index 072df0b61..6338edc1e 100644
--- a/Ryujinx.HLE/HOS/ArmProcessContext.cs
+++ b/Ryujinx.HLE/HOS/ArmProcessContext.cs
@@ -6,7 +6,17 @@ using Ryujinx.Memory;
 
 namespace Ryujinx.HLE.HOS
 {
-    class ArmProcessContext<T> : IProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
+    interface IArmProcessContext : IProcessContext
+    {
+        IDiskCacheLoadState Initialize(
+            string titleIdText,
+            string displayVersion,
+            bool diskCacheEnabled,
+            ulong codeAddress,
+            ulong codeSize);
+    }
+
+    class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
     {
         private readonly ulong _pid;
         private readonly GpuContext _gpuContext;
@@ -40,6 +50,17 @@ namespace Ryujinx.HLE.HOS
             _cpuContext.Execute(context, codeAddress);
         }
 
+        public IDiskCacheLoadState Initialize(
+            string titleIdText,
+            string displayVersion,
+            bool diskCacheEnabled,
+            ulong codeAddress,
+            ulong codeSize)
+        {
+            _cpuContext.PrepareCodeRange(codeAddress, codeSize);
+            return _cpuContext.LoadDiskCache(titleIdText, displayVersion, diskCacheEnabled);
+        }
+
         public void InvalidateCacheRegion(ulong address, ulong size)
         {
             _cpuContext.InvalidateCacheRegion(address, size);
diff --git a/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs b/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
index 7d1c4e1d6..5ecaf38e9 100644
--- a/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
+++ b/Ryujinx.HLE/HOS/ArmProcessContextFactory.cs
@@ -13,11 +13,30 @@ namespace Ryujinx.HLE.HOS
     {
         private readonly ICpuEngine _cpuEngine;
         private readonly GpuContext _gpu;
+        private readonly string _titleIdText;
+        private readonly string _displayVersion;
+        private readonly bool _diskCacheEnabled;
+        private readonly ulong _codeAddress;
+        private readonly ulong _codeSize;
 
-        public ArmProcessContextFactory(ICpuEngine cpuEngine, GpuContext gpu)
+        public IDiskCacheLoadState DiskCacheLoadState { get; private set; }
+
+        public ArmProcessContextFactory(
+            ICpuEngine cpuEngine,
+            GpuContext gpu,
+            string titleIdText,
+            string displayVersion,
+            bool diskCacheEnabled,
+            ulong codeAddress,
+            ulong codeSize)
         {
             _cpuEngine = cpuEngine;
             _gpu = gpu;
+            _titleIdText = titleIdText;
+            _displayVersion = displayVersion;
+            _diskCacheEnabled = diskCacheEnabled;
+            _codeAddress = codeAddress;
+            _codeSize = codeSize;
         }
 
         public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
@@ -29,21 +48,29 @@ namespace Ryujinx.HLE.HOS
                 mode = MemoryManagerMode.SoftwarePageTable;
             }
 
+            IArmProcessContext processContext;
+
             switch (mode)
             {
                 case MemoryManagerMode.SoftwarePageTable:
                     var memoryManager = new MemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
-                    return new ArmProcessContext<MemoryManager>(pid, _cpuEngine, _gpu, memoryManager, for64Bit);
+                    processContext = new ArmProcessContext<MemoryManager>(pid, _cpuEngine, _gpu, memoryManager, for64Bit);
+                    break;
 
                 case MemoryManagerMode.HostMapped:
                 case MemoryManagerMode.HostMappedUnsafe:
                     bool unsafeMode = mode == MemoryManagerMode.HostMappedUnsafe;
                     var memoryManagerHostMapped = new MemoryManagerHostMapped(context.Memory, addressSpaceSize, unsafeMode, invalidAccessHandler);
-                    return new ArmProcessContext<MemoryManagerHostMapped>(pid, _cpuEngine, _gpu, memoryManagerHostMapped, for64Bit);
+                    processContext = new ArmProcessContext<MemoryManagerHostMapped>(pid, _cpuEngine, _gpu, memoryManagerHostMapped, for64Bit);
+                    break;
 
                 default:
                     throw new ArgumentOutOfRangeException();
             }
+
+            DiskCacheLoadState = processContext.Initialize(_titleIdText, _displayVersion, _diskCacheEnabled, _codeAddress, _codeSize);
+
+            return processContext;
         }
     }
 }
diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs
index b422fef75..09e1ac31c 100644
--- a/Ryujinx.HLE/HOS/ProgramLoader.cs
+++ b/Ryujinx.HLE/HOS/ProgramLoader.cs
@@ -1,9 +1,9 @@
-using ARMeilleure.Translation.PTC;
 using LibHac.Loader;
 using LibHac.Ncm;
 using LibHac.Util;
 using Ryujinx.Common;
 using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
 using Ryujinx.HLE.HOS.Kernel;
 using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Memory;
@@ -21,16 +21,40 @@ namespace Ryujinx.HLE.HOS
     {
         public string Name;
         public ulong ProgramId;
-        public bool AllowCodeMemoryForJit;
+        public readonly string TitleIdText;
+        public readonly string DisplayVersion;
+        public readonly bool DiskCacheEnabled;
+        public readonly bool AllowCodeMemoryForJit;
 
-        public ProgramInfo(in Npdm npdm, bool allowCodeMemoryForJit)
+        public ProgramInfo(in Npdm npdm, string displayVersion, bool diskCacheEnabled, bool allowCodeMemoryForJit)
         {
+            ulong programId = npdm.Aci.Value.ProgramId.Value;
+
             Name = StringUtils.Utf8ZToString(npdm.Meta.Value.ProgramName);
-            ProgramId = npdm.Aci.Value.ProgramId.Value;
+            ProgramId = programId;
+            TitleIdText = programId.ToString("x16");
+            DisplayVersion = displayVersion;
+            DiskCacheEnabled = diskCacheEnabled;
             AllowCodeMemoryForJit = allowCodeMemoryForJit;
         }
     }
 
+    struct ProgramLoadResult
+    {
+        public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null);
+
+        public readonly bool Success;
+        public readonly ProcessTamperInfo TamperInfo;
+        public readonly IDiskCacheLoadState DiskCacheLoadState;
+
+        public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState)
+        {
+            Success = success;
+            TamperInfo = tamperInfo;
+            DiskCacheLoadState = diskCacheLoadState;
+        }
+    }
+
     static class ProgramLoader
     {
         private const bool AslrEnabled = true;
@@ -102,7 +126,14 @@ namespace Ryujinx.HLE.HOS
 
             KProcess process = new KProcess(context);
 
-            var processContextFactory = new ArmProcessContextFactory(context.Device.System.CpuEngine, context.Device.Gpu);
+            var processContextFactory = new ArmProcessContextFactory(
+                context.Device.System.CpuEngine,
+                context.Device.Gpu,
+                string.Empty,
+                string.Empty,
+                false,
+                codeAddress,
+                codeSize);
 
             result = process.InitializeKip(
                 creationInfo,
@@ -144,9 +175,8 @@ namespace Ryujinx.HLE.HOS
             return true;
         }
 
-        public static bool LoadNsos(
+        public static ProgramLoadResult LoadNsos(
             KernelContext context,
-            out ProcessTamperInfo tamperInfo,
             MetaLoader metaData,
             ProgramInfo programInfo,
             byte[] arguments = null,
@@ -156,8 +186,7 @@ namespace Ryujinx.HLE.HOS
 
             if (rc.IsFailure())
             {
-                tamperInfo = null;
-                return false;
+                return ProgramLoadResult.Failed;
             }
 
             ref readonly var meta = ref npdm.Meta.Value;
@@ -212,9 +241,6 @@ namespace Ryujinx.HLE.HOS
                 }
             }
 
-            PtcProfiler.StaticCodeStart = codeStart;
-            PtcProfiler.StaticCodeSize  = (ulong)codeSize;
-
             int codePagesCount = (int)(codeSize / KPageTableBase.PageSize);
 
             int personalMmHeapPagesCount = (int)(meta.SystemResourceSize / KPageTableBase.PageSize);
@@ -263,9 +289,7 @@ namespace Ryujinx.HLE.HOS
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed setting resource limit values.");
 
-                tamperInfo = null;
-
-                return false;
+                return ProgramLoadResult.Failed;
             }
 
             KProcess process = new KProcess(context, programInfo.AllowCodeMemoryForJit);
@@ -276,12 +300,17 @@ namespace Ryujinx.HLE.HOS
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization failed due to invalid ACID flags.");
 
-                tamperInfo = null;
-
-                return false;
+                return ProgramLoadResult.Failed;
             }
 
-            var processContextFactory = new ArmProcessContextFactory(context.Device.System.CpuEngine, context.Device.Gpu);
+            var processContextFactory = new ArmProcessContextFactory(
+                context.Device.System.CpuEngine,
+                context.Device.Gpu,
+                programInfo.TitleIdText,
+                programInfo.DisplayVersion,
+                programInfo.DiskCacheEnabled,
+                codeStart,
+                codeSize);
 
             result = process.Initialize(
                 creationInfo,
@@ -294,9 +323,7 @@ namespace Ryujinx.HLE.HOS
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
 
-                tamperInfo = null;
-
-                return false;
+                return ProgramLoadResult.Failed;
             }
 
             for (int index = 0; index < executables.Length; index++)
@@ -309,9 +336,7 @@ namespace Ryujinx.HLE.HOS
                 {
                     Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
 
-                    tamperInfo = null;
-
-                    return false;
+                    return ProgramLoadResult.Failed;
                 }
             }
 
@@ -323,9 +348,7 @@ namespace Ryujinx.HLE.HOS
             {
                 Logger.Error?.Print(LogClass.Loader, $"Process start returned error \"{result}\".");
 
-                tamperInfo = null;
-
-                return false;
+                return ProgramLoadResult.Failed;
             }
 
             context.Processes.TryAdd(process.Pid, process);
@@ -333,10 +356,15 @@ namespace Ryujinx.HLE.HOS
             // Keep the build ids because the tamper machine uses them to know which process to associate a
             // tamper to and also keep the starting address of each executable inside a process because some
             // memory modifications are relative to this address.
-            tamperInfo = new ProcessTamperInfo(process, buildIds, nsoBase, process.MemoryManager.HeapRegionStart,
-                process.MemoryManager.AliasRegionStart, process.MemoryManager.CodeRegionStart);
+            ProcessTamperInfo tamperInfo = new ProcessTamperInfo(
+                process,
+                buildIds,
+                nsoBase,
+                process.MemoryManager.HeapRegionStart,
+                process.MemoryManager.AliasRegionStart,
+                process.MemoryManager.CodeRegionStart);
 
-            return true;
+            return new ProgramLoadResult(true, tamperInfo, processContextFactory.DiskCacheLoadState);
         }
 
         private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
diff --git a/Ryujinx.Headless.SDL2/Program.cs b/Ryujinx.Headless.SDL2/Program.cs
index 4a2ba99de..b0c29e561 100644
--- a/Ryujinx.Headless.SDL2/Program.cs
+++ b/Ryujinx.Headless.SDL2/Program.cs
@@ -1,5 +1,4 @@
 using ARMeilleure.Translation;
-using ARMeilleure.Translation.PTC;
 using CommandLine;
 using LibHac.Tools.FsSystem;
 using Ryujinx.Audio.Backends.SDL2;
@@ -12,6 +11,7 @@ using Ryujinx.Common.Configuration.Hid.Keyboard;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.SystemInterop;
 using Ryujinx.Common.Utilities;
+using Ryujinx.Cpu;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL.Multithreading;
 using Ryujinx.Graphics.Gpu;
@@ -447,8 +447,11 @@ namespace Ryujinx.Headless.SDL2
 
         private static void SetupProgressHandler()
         {
-            Ptc.PtcStateChanged -= ProgressHandler;
-            Ptc.PtcStateChanged += ProgressHandler;
+            if (_emulationContext.Application.DiskCacheLoadState != null)
+            {
+                _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
+                _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
+            }
 
             _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
             _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
@@ -460,7 +463,7 @@ namespace Ryujinx.Headless.SDL2
 
             switch (state)
             {
-                case PtcLoadingState ptcState:
+                case LoadState ptcState:
                     label = $"PTC : {current}/{total}";
                     break;
                 case ShaderCacheState shaderCacheState:
@@ -563,9 +566,6 @@ namespace Ryujinx.Headless.SDL2
 
             _window.Execute();
 
-            Ptc.Close();
-            PtcProfiler.Stop();
-
             _emulationContext.Dispose();
             _window.Dispose();
 
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index e27c4ae94..787a8ad5f 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -1,4 +1,3 @@
-using ARMeilleure.Translation.PTC;
 using Gtk;
 using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
@@ -308,9 +307,6 @@ namespace Ryujinx
 
         private static void ProcessUnhandledException(Exception ex, bool isTerminating)
         {
-            Ptc.Close();
-            PtcProfiler.Stop();
-
             string message = $"Unhandled exception caught: {ex}";
 
             Logger.Error?.PrintMsg(LogClass.Application, message);
@@ -330,9 +326,6 @@ namespace Ryujinx
         {
             DiscordIntegrationModule.Exit();
 
-            Ptc.Dispose();
-            PtcProfiler.Dispose();
-
             Logger.Shutdown();
         }
     }
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 0e7e4d625..495f66519 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -1,5 +1,4 @@
 using ARMeilleure.Translation;
-using ARMeilleure.Translation.PTC;
 using Gtk;
 using LibHac.Common;
 using LibHac.Common.Keys;
@@ -16,6 +15,7 @@ using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using Ryujinx.Common.SystemInterop;
+using Ryujinx.Cpu;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.GAL.Multithreading;
 using Ryujinx.Graphics.OpenGL;
@@ -46,7 +46,6 @@ using System.Threading;
 using System.Threading.Tasks;
 
 using GUI = Gtk.Builder.ObjectAttribute;
-using PtcLoadingState = ARMeilleure.Translation.PTC.PtcLoadingState;
 using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState;
 
 namespace Ryujinx.Ui
@@ -588,8 +587,11 @@ namespace Ryujinx.Ui
 
         private void SetupProgressUiHandlers()
         {
-            Ptc.PtcStateChanged -= ProgressHandler;
-            Ptc.PtcStateChanged += ProgressHandler;
+            if (_emulationContext.Application.DiskCacheLoadState != null)
+            {
+                _emulationContext.Application.DiskCacheLoadState.StateChanged -= ProgressHandler;
+                _emulationContext.Application.DiskCacheLoadState.StateChanged += ProgressHandler;
+            }
 
             _emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler;
             _emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler;
@@ -602,8 +604,8 @@ namespace Ryujinx.Ui
 
             switch (state)
             {
-                case PtcLoadingState ptcState:
-                    visible = ptcState != PtcLoadingState.Loaded;
+                case LoadState ptcState:
+                    visible = ptcState != LoadState.Loaded;
                     label = $"PTC : {current}/{total}";
                     break;
                 case ShaderCacheLoadingState shaderCacheState:
@@ -705,8 +707,6 @@ namespace Ryujinx.Ui
 
                 UpdateGraphicsConfig();
 
-                SetupProgressUiHandlers();
-
                 SystemVersion firmwareVersion = _contentManager.GetCurrentFirmwareVersion();
 
                 bool isDirectory     = Directory.Exists(path);
@@ -841,6 +841,8 @@ namespace Ryujinx.Ui
                     return;
                 }
 
+                SetupProgressUiHandlers();
+
                 _currentEmulatedGamePath = path;
 
                 _deviceExitStatus.Reset();
@@ -967,9 +969,6 @@ namespace Ryujinx.Ui
 
             RendererWidget.Start();
 
-            Ptc.Close();
-            PtcProfiler.Stop();
-
             _emulationContext.Dispose();
             _deviceExitStatus.Set();
 
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index 9dabe8173..8db023bec 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -1,5 +1,4 @@
 using ARMeilleure.Translation;
-using ARMeilleure.Translation.PTC;
 using Gdk;
 using Gtk;
 using Ryujinx.Common;
@@ -519,7 +518,7 @@ namespace Ryujinx.Ui
             _gpuCancellationTokenSource.Cancel();
 
             _isStopped = true;
-            
+
             if (_isActive)
             {
                 _isActive = false;
@@ -585,7 +584,7 @@ namespace Ryujinx.Ui
                     {
                         if (!ParentWindow.State.HasFlag(WindowState.Fullscreen))
                         {
-                            Ptc.Continue();
+                            Device.Application.DiskCacheLoadState?.Cancel();
                         }
                     }
                 });