From b5c215111de665ef8d18b38405ac55e17996e30e Mon Sep 17 00:00:00 2001
From: LDj3SNuD <35856442+LDj3SNuD@users.noreply.github.com>
Date: Thu, 17 Dec 2020 20:32:09 +0100
Subject: [PATCH] PPTC Follow-up. (#1712)

* Added support for offline invalidation, via PPTC, of low cq translations replaced by high cq translations; both on a single run and between runs.

Added invalidation of .cache files in the event of reuse on a different user operating system.

Added .info and .cache files invalidation in case of a failed stream decompression.

Nits.

* InternalVersion = 1712;

* Nits.

* Address comment.

* Get rid of BinaryFormatter.

Nits.

* Move Ptc.LoadTranslations().

Nits.

* Nits.

* Fixed corner cases (in case backup copies have to be used). Added save logs.

* Not core fixes.

* Complement to the previous commit. Added load logs. Removed BinaryFormatter leftovers.

* Add LoadTranslations log.

* Nits.

* Removed the search and management of LowCq overlapping functions.

* Final increment of .info and .cache flags.

* Nit.

* GetIndirectFunctionAddress(): Validate that writing actually takes place in dynamic table memory range (and not elsewhere).

* Fix Ptc.UpdateInfo() due to rebase.

* Nit for retrigger Checks.

* Nit for retrigger Checks.
---
 ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs   |   2 +
 .../CodeGen/Unwinding/UnwindPushEntry.cs      |   2 +
 ARMeilleure/Decoders/Decoder.cs               |   2 +
 ARMeilleure/Instructions/NativeInterface.cs   |   3 +
 ARMeilleure/State/ExecutionMode.cs            |   6 +-
 ARMeilleure/Translation/Cache/JumpTable.cs    |  65 ++--
 ARMeilleure/Translation/DelegateInfo.cs       |   2 +-
 .../Translation/JumpTableEntryAllocator.cs    |  72 +++++
 ARMeilleure/Translation/PTC/Ptc.cs            | 233 ++++++++++-----
 ARMeilleure/Translation/PTC/PtcInfo.cs        |   2 +-
 ARMeilleure/Translation/PTC/PtcJumpTable.cs   | 281 +++++++++++++++---
 ARMeilleure/Translation/PTC/PtcProfiler.cs    | 159 ++++++++--
 ARMeilleure/Translation/PTC/RelocEntry.cs     |   2 +
 ARMeilleure/Translation/SsaDeconstruction.cs  |   1 -
 ARMeilleure/Translation/Translator.cs         |   5 +-
 Ryujinx.HLE/HOS/ProgramLoader.cs              |   2 +-
 16 files changed, 670 insertions(+), 169 deletions(-)
 create mode 100644 ARMeilleure/Translation/JumpTableEntryAllocator.cs

diff --git a/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs b/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs
index 8072acd953..3d0bc21d58 100644
--- a/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs
+++ b/ARMeilleure/CodeGen/Unwinding/UnwindInfo.cs
@@ -2,6 +2,8 @@ namespace ARMeilleure.CodeGen.Unwinding
 {
     struct UnwindInfo
     {
+        public const int Stride = 4; // Bytes.
+
         public UnwindPushEntry[] PushEntries { get; }
         public int PrologSize { get; }
 
diff --git a/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs b/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs
index 021479a4f2..fd8ea402b2 100644
--- a/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs
+++ b/ARMeilleure/CodeGen/Unwinding/UnwindPushEntry.cs
@@ -2,6 +2,8 @@ namespace ARMeilleure.CodeGen.Unwinding
 {
     struct UnwindPushEntry
     {
+        public const int Stride = 16; // Bytes.
+
         public UnwindPseudoOp PseudoOp { get; }
         public int PrologOffset { get; }
         public int RegIndex { get; }
diff --git a/ARMeilleure/Decoders/Decoder.cs b/ARMeilleure/Decoders/Decoder.cs
index 8666cdccc2..eb085999a0 100644
--- a/ARMeilleure/Decoders/Decoder.cs
+++ b/ARMeilleure/Decoders/Decoder.cs
@@ -26,6 +26,8 @@ namespace ARMeilleure.Decoders
 
             Dictionary<ulong, Block> visited = new Dictionary<ulong, Block>();
 
+            Debug.Assert(MaxInstsPerFunctionLowCq <= MaxInstsPerFunction);
+
             int opsCount = 0;
 
             int instructionLimit = highCq ? MaxInstsPerFunction : MaxInstsPerFunctionLowCq;
diff --git a/ARMeilleure/Instructions/NativeInterface.cs b/ARMeilleure/Instructions/NativeInterface.cs
index b2b200b90e..b8b7ff0e83 100644
--- a/ARMeilleure/Instructions/NativeInterface.cs
+++ b/ARMeilleure/Instructions/NativeInterface.cs
@@ -2,6 +2,7 @@ using ARMeilleure.Memory;
 using ARMeilleure.State;
 using ARMeilleure.Translation;
 using System;
+using System.Diagnostics;
 using System.Runtime.InteropServices;
 
 namespace ARMeilleure.Instructions
@@ -254,6 +255,8 @@ namespace ARMeilleure.Instructions
 
             if (function.HighCq)
             {
+                Debug.Assert(Context.Translator.JumpTable.CheckEntryFromAddressDynamicTable((IntPtr)entryAddress));
+
                 // Rewrite the host function address in the table to point to the highCq function.
                 Marshal.WriteInt64((IntPtr)entryAddress, 8, (long)ptr);
             }
diff --git a/ARMeilleure/State/ExecutionMode.cs b/ARMeilleure/State/ExecutionMode.cs
index eaed9d27f1..f43c5569f5 100644
--- a/ARMeilleure/State/ExecutionMode.cs
+++ b/ARMeilleure/State/ExecutionMode.cs
@@ -2,8 +2,8 @@ namespace ARMeilleure.State
 {
     enum ExecutionMode
     {
-        Aarch32Arm,
-        Aarch32Thumb,
-        Aarch64
+        Aarch32Arm = 0,
+        Aarch32Thumb = 1,
+        Aarch64 = 2
     }
 }
\ No newline at end of file
diff --git a/ARMeilleure/Translation/Cache/JumpTable.cs b/ARMeilleure/Translation/Cache/JumpTable.cs
index 71a036d83f..aa3b6caf30 100644
--- a/ARMeilleure/Translation/Cache/JumpTable.cs
+++ b/ARMeilleure/Translation/Cache/JumpTable.cs
@@ -14,7 +14,7 @@ namespace ARMeilleure.Translation.Cache
         // The jump table is a block of (guestAddress, hostAddress) function mappings.
         // Each entry corresponds to one branch in a JIT compiled function. The entries are
         // reserved specifically for each call.
-        // The _dependants dictionary can be used to update the hostAddress for any functions that change.
+        // The Dependants dictionary can be used to update the hostAddress for any functions that change.
 
         public const int JumpTableStride = 16; // 8 byte guest address, 8 byte host address.
 
@@ -42,7 +42,7 @@ namespace ARMeilleure.Translation.Cache
         private const int DynamicTableSize = 1048576;
         private const int DynamicTableByteSize = DynamicTableSize * DynamicTableStride;
 
-        private const int DynamicEntryTag = 1 << 31;
+        public const int DynamicEntryTag = 1 << 31;
 
         private readonly ReservedRegion _jumpRegion;
         private readonly ReservedRegion _dynamicRegion;
@@ -87,14 +87,14 @@ namespace ARMeilleure.Translation.Cache
                 }
             }
 
-            foreach (var item in ptcJumpTable.Dependants)
+            foreach (var kv in ptcJumpTable.Dependants)
             {
-                Dependants.TryAdd(item.Key, new List<int>(item.Value));
+                Dependants.TryAdd(kv.Key, new List<int>(kv.Value));
             }
 
-            foreach (var item in ptcJumpTable.Owners)
+            foreach (var kv in ptcJumpTable.Owners)
             {
-                Owners.TryAdd(item.Key, new List<int>(item.Value));
+                Owners.TryAdd(kv.Key, new List<int>(kv.Value));
             }
         }
 
@@ -182,29 +182,14 @@ namespace ARMeilleure.Translation.Cache
         // For future use.
         public void RemoveFunctionEntries(ulong guestAddress)
         {
-            if (Owners.TryRemove(guestAddress, out List<int> list))
+            Targets.TryRemove(guestAddress, out _);
+            Dependants.TryRemove(guestAddress, out _);
+
+            if (Owners.TryRemove(guestAddress, out List<int> entries))
             {
-                for (int i = 0; i < list.Count; i++)
+                foreach (int entry in entries)
                 {
-                    int entry = list[i];
-
-                    bool isDynamic = (entry & DynamicEntryTag) != 0;
-
-                    entry &= ~DynamicEntryTag;
-
-                    if (isDynamic)
-                    {
-                        IntPtr addr = GetEntryAddressDynamicTable(entry);
-
-                        for (int j = 0; j < DynamicTableElems; j++)
-                        {
-                            Marshal.WriteInt64(addr + j * JumpTableStride, 0, 0L);
-                            Marshal.WriteInt64(addr + j * JumpTableStride, 8, 0L);
-                        }
-
-                        DynTable.FreeEntry(entry);
-                    }
-                    else
+                    if ((entry & DynamicEntryTag) == 0)
                     {
                         IntPtr addr = GetEntryAddressJumpTable(entry);
 
@@ -213,6 +198,18 @@ namespace ARMeilleure.Translation.Cache
 
                         Table.FreeEntry(entry);
                     }
+                    else
+                    {
+                        IntPtr addr = GetEntryAddressDynamicTable(entry & ~DynamicEntryTag);
+
+                        for (int j = 0; j < DynamicTableElems; j++)
+                        {
+                            Marshal.WriteInt64(addr + j * JumpTableStride, 0, 0L);
+                            Marshal.WriteInt64(addr + j * JumpTableStride, 8, 0L);
+                        }
+
+                        DynTable.FreeEntry(entry & ~DynamicEntryTag);
+                    }
                 }
             }
         }
@@ -259,6 +256,20 @@ namespace ARMeilleure.Translation.Cache
             return _dynamicRegion.Pointer + entry * DynamicTableStride;
         }
 
+        public bool CheckEntryFromAddressJumpTable(IntPtr entryAddress)
+        {
+            int entry = Math.DivRem((int)((ulong)entryAddress - (ulong)_jumpRegion.Pointer), JumpTableStride, out int rem);
+
+            return rem == 0 && Table.EntryIsValid(entry);
+        }
+
+        public bool CheckEntryFromAddressDynamicTable(IntPtr entryAddress)
+        {
+            int entry = Math.DivRem((int)((ulong)entryAddress - (ulong)_dynamicRegion.Pointer), DynamicTableStride, out int rem);
+
+            return rem == 0 && DynTable.EntryIsValid(entry);
+        }
+
         public void Dispose()
         {
             _jumpRegion.Dispose();
diff --git a/ARMeilleure/Translation/DelegateInfo.cs b/ARMeilleure/Translation/DelegateInfo.cs
index e68cfc1b76..36320ac317 100644
--- a/ARMeilleure/Translation/DelegateInfo.cs
+++ b/ARMeilleure/Translation/DelegateInfo.cs
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
 
 namespace ARMeilleure.Translation
 {
-    sealed class DelegateInfo
+    class DelegateInfo
     {
         private readonly Delegate _dlg; // Ensure that this delegate will not be garbage collected.
 
diff --git a/ARMeilleure/Translation/JumpTableEntryAllocator.cs b/ARMeilleure/Translation/JumpTableEntryAllocator.cs
new file mode 100644
index 0000000000..3b918628ff
--- /dev/null
+++ b/ARMeilleure/Translation/JumpTableEntryAllocator.cs
@@ -0,0 +1,72 @@
+using ARMeilleure.Common;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace ARMeilleure.Translation
+{
+    class JumpTableEntryAllocator
+    {
+        private readonly BitMap _bitmap;
+        private int _freeHint;
+
+        public JumpTableEntryAllocator()
+        {
+            _bitmap = new BitMap();
+        }
+
+        public bool EntryIsValid(int entryIndex)
+        {
+            lock (_bitmap)
+            {
+                return _bitmap.IsSet(entryIndex);
+            }
+        }
+
+        public void SetEntry(int entryIndex)
+        {
+            lock (_bitmap)
+            {
+                _bitmap.Set(entryIndex);
+            }
+        }
+
+        public int AllocateEntry()
+        {
+            lock (_bitmap)
+            {
+                int entryIndex;
+
+                if (!_bitmap.IsSet(_freeHint))
+                {
+                    entryIndex = _freeHint;
+                }
+                else
+                {
+                    entryIndex = _bitmap.FindFirstUnset();
+                }
+
+                _freeHint = entryIndex + 1;
+
+                bool wasSet = _bitmap.Set(entryIndex);
+                Debug.Assert(wasSet);
+
+                return entryIndex;
+            }
+        }
+
+        public void FreeEntry(int entryIndex)
+        {
+            lock (_bitmap)
+            {
+                _bitmap.Clear(entryIndex);
+
+                _freeHint = entryIndex;
+            }
+        }
+
+        public IEnumerable<int> GetEntries()
+        {
+            return _bitmap;
+        }
+    }
+}
diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs
index 1cdaa8fe22..3150c97c5c 100644
--- a/ARMeilleure/Translation/PTC/Ptc.cs
+++ b/ARMeilleure/Translation/PTC/Ptc.cs
@@ -12,7 +12,7 @@ using System.Diagnostics;
 using System.IO;
 using System.IO.Compression;
 using System.Runtime.InteropServices;
-using System.Runtime.Serialization.Formatters.Binary;
+using System.Security.Cryptography;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -22,7 +22,7 @@ namespace ARMeilleure.Translation.PTC
     {
         private const string HeaderMagic = "PTChd";
 
-        private const int InternalVersion = 1519; //! To be incremented manually for each change to the ARMeilleure project.
+        private const uint InternalVersion = 1713; //! To be incremented manually for each change to the ARMeilleure project.
 
         private const string ActualDir = "0";
         private const string BackupDir = "1";
@@ -34,6 +34,7 @@ namespace ARMeilleure.Translation.PTC
         internal const int JumpPointerIndex = -2; // Must be a negative value.
         internal const int DynamicPointerIndex = -3; // Must be a negative value.
 
+        private const byte FillingByte = 0x00;
         private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
 
         private static readonly MemoryStream _infosStream;
@@ -43,8 +44,6 @@ namespace ARMeilleure.Translation.PTC
 
         private static readonly BinaryWriter _infosWriter;
 
-        private static readonly BinaryFormatter _binaryFormatter;
-
         private static readonly ManualResetEvent _waitEvent;
 
         private static readonly AutoResetEvent _loggerEvent;
@@ -54,7 +53,6 @@ namespace ARMeilleure.Translation.PTC
         private static bool _disposed;
 
         private static volatile int _translateCount;
-        private static volatile int _rejitCount;
 
         internal static PtcJumpTable PtcJumpTable { get; private set; }
 
@@ -75,8 +73,6 @@ namespace ARMeilleure.Translation.PTC
 
             _infosWriter = new BinaryWriter(_infosStream, EncodingCache.UTF8NoBOM, true);
 
-            _binaryFormatter = new BinaryFormatter();
-
             _waitEvent = new ManualResetEvent(true);
 
             _loggerEvent = new AutoResetEvent(false);
@@ -174,26 +170,26 @@ namespace ARMeilleure.Translation.PTC
 
             if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
             {
-                if (!Load(fileNameActual))
+                if (!Load(fileNameActual, false))
                 {
                     if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
                     {
-                        Load(fileNameBackup);
+                        Load(fileNameBackup, true);
                     }
                 }
             }
             else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
             {
-                Load(fileNameBackup);
+                Load(fileNameBackup, true);
             }
         }
 
-        private static bool Load(string fileName)
+        private static bool Load(string fileName, bool isBackup)
         {
             using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
             using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
             using (MemoryStream stream = new MemoryStream())
-            using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
+            using (MD5 md5 = MD5.Create())
             {
                 int hashSize = md5.HashSize / 8;
 
@@ -247,6 +243,13 @@ namespace ARMeilleure.Translation.PTC
                     return false;
                 }
 
+                if (header.OSPlatform != GetOSPlatform())
+                {
+                    InvalidateCompressedStream(compressedStream);
+
+                    return false;
+                }
+
                 if (header.InfosLen % InfoEntry.Stride != 0)
                 {
                     InvalidateCompressedStream(compressedStream);
@@ -266,7 +269,7 @@ namespace ARMeilleure.Translation.PTC
 
                 try
                 {
-                    PtcJumpTable = (PtcJumpTable)_binaryFormatter.Deserialize(stream);
+                    PtcJumpTable = PtcJumpTable.Deserialize(stream);
                 }
                 catch
                 {
@@ -281,9 +284,13 @@ namespace ARMeilleure.Translation.PTC
                 _codesStream.Write(codesBuf, 0, header.CodesLen);
                 _relocsStream.Write(relocsBuf, 0, header.RelocsLen);
                 _unwindInfosStream.Write(unwindInfosBuf, 0, header.UnwindInfosLen);
-
-                return true;
             }
+
+            long fileSize = new FileInfo(fileName).Length;
+
+            Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Translation Cache" : "Loaded Translation Cache")} (size: {fileSize} bytes, translated functions: {GetInfosEntriesCount()}).");
+
+            return true;
         }
 
         private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
@@ -299,8 +306,9 @@ namespace ARMeilleure.Translation.PTC
 
                 header.Magic = headerReader.ReadString();
 
-                header.CacheFileVersion = headerReader.ReadInt32();
+                header.CacheFileVersion = headerReader.ReadUInt32();
                 header.FeatureInfo = headerReader.ReadUInt64();
+                header.OSPlatform = headerReader.ReadUInt32();
 
                 header.InfosLen = headerReader.ReadInt32();
                 header.CodesLen = headerReader.ReadInt32();
@@ -338,7 +346,7 @@ namespace ARMeilleure.Translation.PTC
         private static void Save(string fileName)
         {
             using (MemoryStream stream = new MemoryStream())
-            using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
+            using (MD5 md5 = MD5.Create())
             {
                 int hashSize = md5.HashSize / 8;
 
@@ -351,7 +359,7 @@ namespace ARMeilleure.Translation.PTC
                 _relocsStream.WriteTo(stream);
                 _unwindInfosStream.WriteTo(stream);
 
-                _binaryFormatter.Serialize(stream, PtcJumpTable);
+                PtcJumpTable.Serialize(stream, PtcJumpTable);
 
                 stream.Seek((long)hashSize, SeekOrigin.Begin);
                 byte[] hash = md5.ComputeHash(stream);
@@ -377,6 +385,10 @@ namespace ARMeilleure.Translation.PTC
                     }
                 }
             }
+
+            long fileSize = new FileInfo(fileName).Length;
+
+            Logger.Info?.Print(LogClass.Ptc, $"Saved Translation Cache (size: {fileSize} bytes, translated functions: {GetInfosEntriesCount()}).");
         }
 
         private static void WriteHeader(MemoryStream stream)
@@ -385,8 +397,9 @@ namespace ARMeilleure.Translation.PTC
             {
                 headerWriter.Write((string)HeaderMagic); // Header.Magic
 
-                headerWriter.Write((int)InternalVersion); // Header.CacheFileVersion
+                headerWriter.Write((uint)InternalVersion); // Header.CacheFileVersion
                 headerWriter.Write((ulong)GetFeatureInfo()); // Header.FeatureInfo
+                headerWriter.Write((uint)GetOSPlatform()); // Header.OSPlatform
 
                 headerWriter.Write((int)_infosStream.Length); // Header.InfosLen
                 headerWriter.Write((int)_codesStream.Length); // Header.CodesLen
@@ -395,7 +408,7 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        internal static void LoadTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IntPtr pageTablePointer, JumpTable jumpTable)
+        internal static void LoadTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IMemoryManager memory, JumpTable jumpTable)
         {
             if ((int)_infosStream.Length == 0 ||
                 (int)_codesStream.Length == 0 ||
@@ -417,26 +430,44 @@ namespace ARMeilleure.Translation.PTC
             using (BinaryReader relocsReader = new BinaryReader(_relocsStream, EncodingCache.UTF8NoBOM, true))
             using (BinaryReader unwindInfosReader = new BinaryReader(_unwindInfosStream, EncodingCache.UTF8NoBOM, true))
             {
-                int infosEntriesCount = (int)_infosStream.Length / InfoEntry.Stride;
-
-                for (int i = 0; i < infosEntriesCount; i++)
+                for (int i = 0; i < GetInfosEntriesCount(); i++)
                 {
                     InfoEntry infoEntry = ReadInfo(infosReader);
 
-                    byte[] code = ReadCode(codesReader, infoEntry.CodeLen);
-
-                    if (infoEntry.RelocEntriesCount != 0)
+                    if (infoEntry.Stubbed)
                     {
-                        RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount);
-
-                        PatchCode(code, relocEntries, pageTablePointer, jumpTable);
+                        SkipCode(infoEntry.CodeLen);
+                        SkipReloc(infoEntry.RelocEntriesCount);
+                        SkipUnwindInfo(unwindInfosReader);
                     }
+                    else if (infoEntry.HighCq || !PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) || !value.highCq)
+                    {
+                        byte[] code = ReadCode(codesReader, infoEntry.CodeLen);
 
-                    UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader);
+                        if (infoEntry.RelocEntriesCount != 0)
+                        {
+                            RelocEntry[] relocEntries = GetRelocEntries(relocsReader, infoEntry.RelocEntriesCount);
 
-                    TranslatedFunction func = FastTranslate(code, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq);
+                            PatchCode(code, relocEntries, memory.PageTablePointer, jumpTable);
+                        }
 
-                    funcs.AddOrUpdate(infoEntry.Address, func, (key, oldFunc) => func.HighCq && !oldFunc.HighCq ? func : oldFunc);
+                        UnwindInfo unwindInfo = ReadUnwindInfo(unwindInfosReader);
+
+                        TranslatedFunction func = FastTranslate(code, infoEntry.GuestSize, unwindInfo, infoEntry.HighCq);
+
+                        bool isAddressUnique = funcs.TryAdd(infoEntry.Address, func);
+
+                        Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
+                    }
+                    else
+                    {
+                        infoEntry.Stubbed = true;
+                        UpdateInfo(infoEntry);
+
+                        StubCode(infoEntry.CodeLen);
+                        StubReloc(infoEntry.RelocEntriesCount);
+                        StubUnwindInfo(unwindInfosReader);
+                    }
                 }
             }
 
@@ -452,6 +483,13 @@ namespace ARMeilleure.Translation.PTC
 
             PtcJumpTable.WriteJumpTable(jumpTable, funcs);
             PtcJumpTable.WriteDynamicTable(jumpTable);
+
+            Logger.Info?.Print(LogClass.Ptc, $"{funcs.Count} translated functions loaded");
+        }
+
+        private static int GetInfosEntriesCount()
+        {
+            return (int)_infosStream.Length / InfoEntry.Stride;
         }
 
         private static InfoEntry ReadInfo(BinaryReader infosReader)
@@ -461,12 +499,30 @@ namespace ARMeilleure.Translation.PTC
             infoEntry.Address = infosReader.ReadUInt64();
             infoEntry.GuestSize = infosReader.ReadUInt64();
             infoEntry.HighCq = infosReader.ReadBoolean();
+            infoEntry.Stubbed = infosReader.ReadBoolean();
             infoEntry.CodeLen = infosReader.ReadInt32();
             infoEntry.RelocEntriesCount = infosReader.ReadInt32();
 
             return infoEntry;
         }
 
+        private static void SkipCode(int codeLen)
+        {
+            _codesStream.Seek(codeLen, SeekOrigin.Current);
+        }
+
+        private static void SkipReloc(int relocEntriesCount)
+        {
+            _relocsStream.Seek(relocEntriesCount * RelocEntry.Stride, SeekOrigin.Current);
+        }
+
+        private static void SkipUnwindInfo(BinaryReader unwindInfosReader)
+        {
+            int pushEntriesLength = unwindInfosReader.ReadInt32();
+
+            _unwindInfosStream.Seek(pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride, SeekOrigin.Current);
+        }
+
         private static byte[] ReadCode(BinaryReader codesReader, int codeLen)
         {
             byte[] codeBuf = new byte[codeLen];
@@ -556,50 +612,79 @@ namespace ARMeilleure.Translation.PTC
             return tFunc;
         }
 
+        private static void UpdateInfo(InfoEntry infoEntry)
+        {
+            _infosStream.Seek(-InfoEntry.Stride, SeekOrigin.Current);
+
+            // WriteInfo.
+            _infosWriter.Write((ulong)infoEntry.Address);
+            _infosWriter.Write((ulong)infoEntry.GuestSize);
+            _infosWriter.Write((bool)infoEntry.HighCq);
+            _infosWriter.Write((bool)infoEntry.Stubbed);
+            _infosWriter.Write((int)infoEntry.CodeLen);
+            _infosWriter.Write((int)infoEntry.RelocEntriesCount);
+        }
+
+        private static void StubCode(int codeLen)
+        {
+            for (int i = 0; i < codeLen; i++)
+            {
+                _codesStream.WriteByte(FillingByte);
+            }
+        }
+
+        private static void StubReloc(int relocEntriesCount)
+        {
+            for (int i = 0; i < relocEntriesCount * RelocEntry.Stride; i++)
+            {
+                _relocsStream.WriteByte(FillingByte);
+            }
+        }
+
+        private static void StubUnwindInfo(BinaryReader unwindInfosReader)
+        {
+            int pushEntriesLength = unwindInfosReader.ReadInt32();
+
+            for (int i = 0; i < pushEntriesLength * UnwindPushEntry.Stride + UnwindInfo.Stride; i++)
+            {
+                _unwindInfosStream.WriteByte(FillingByte);
+            }
+        }
+
         internal static void MakeAndSaveTranslations(ConcurrentDictionary<ulong, TranslatedFunction> funcs, IMemoryManager memory, JumpTable jumpTable)
         {
-            if (PtcProfiler.ProfiledFuncs.Count == 0)
+            var profiledFuncsToTranslate = PtcProfiler.GetProfiledFuncsToTranslate(funcs);
+
+            if (profiledFuncsToTranslate.Count == 0)
             {
                 return;
             }
 
             _translateCount = 0;
-            _rejitCount = 0;
 
-            ThreadPool.QueueUserWorkItem(TranslationLogger, (funcs.Count, PtcProfiler.ProfiledFuncs.Count));
+            ThreadPool.QueueUserWorkItem(TranslationLogger, profiledFuncsToTranslate.Count);
 
             int maxDegreeOfParallelism = (Environment.ProcessorCount * 3) / 4;
 
-            Parallel.ForEach(PtcProfiler.ProfiledFuncs, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (item, state) =>
+            Parallel.ForEach(profiledFuncsToTranslate, new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, (item, state) =>
             {
                 ulong address = item.Key;
 
                 Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address));
 
-                if (!funcs.ContainsKey(address))
+                TranslatedFunction func = Translator.Translate(memory, jumpTable, address, item.Value.mode, item.Value.highCq);
+
+                bool isAddressUnique = funcs.TryAdd(address, func);
+
+                Debug.Assert(isAddressUnique, $"The address 0x{address:X16} is not unique.");
+
+                if (func.HighCq)
                 {
-                    TranslatedFunction func = Translator.Translate(memory, jumpTable, address, item.Value.mode, item.Value.highCq);
-
-                    funcs.TryAdd(address, func);
-
-                    if (func.HighCq)
-                    {
-                        jumpTable.RegisterFunction(address, func);
-                    }
-
-                    Interlocked.Increment(ref _translateCount);
-                }
-                else if (item.Value.highCq && !funcs[address].HighCq)
-                {
-                    TranslatedFunction func = Translator.Translate(memory, jumpTable, address, item.Value.mode, highCq: true);
-
-                    funcs[address] = func;
-
                     jumpTable.RegisterFunction(address, func);
-
-                    Interlocked.Increment(ref _rejitCount);
                 }
 
+                Interlocked.Increment(ref _translateCount);
+
                 if (State != PtcState.Enabled)
                 {
                     state.Stop();
@@ -608,30 +693,27 @@ namespace ARMeilleure.Translation.PTC
 
             _loggerEvent.Set();
 
-            if (_translateCount != 0 || _rejitCount != 0)
-            {
-                PtcJumpTable.Initialize(jumpTable);
+            PtcJumpTable.Initialize(jumpTable);
 
-                PtcJumpTable.ReadJumpTable(jumpTable);
-                PtcJumpTable.ReadDynamicTable(jumpTable);
+            PtcJumpTable.ReadJumpTable(jumpTable);
+            PtcJumpTable.ReadDynamicTable(jumpTable);
 
-                ThreadPool.QueueUserWorkItem(PreSave);
-            }
+            ThreadPool.QueueUserWorkItem(PreSave);
         }
 
         private static void TranslationLogger(object state)
         {
             const int refreshRate = 1; // Seconds.
 
-            (int funcsCount, int ProfiledFuncsCount) = ((int, int))state;
+            int profiledFuncsToTranslateCount = (int)state;
 
             do
             {
-                Logger.Info?.Print(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
+                Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {profiledFuncsToTranslateCount} functions translated");
             }
             while (!_loggerEvent.WaitOne(refreshRate * 1000));
 
-            Logger.Info?.Print(LogClass.Ptc, $"{funcsCount + _translateCount} of {ProfiledFuncsCount} functions to translate - {_rejitCount} functions rejited");
+            Logger.Info?.Print(LogClass.Ptc, $"{_translateCount} of {profiledFuncsToTranslateCount} functions translated");
         }
 
         internal static void WriteInfoCodeReloc(ulong address, ulong guestSize, bool highCq, PtcInfo ptcInfo)
@@ -642,6 +724,7 @@ namespace ARMeilleure.Translation.PTC
                 _infosWriter.Write((ulong)address); // InfoEntry.Address
                 _infosWriter.Write((ulong)guestSize); // InfoEntry.GuestSize
                 _infosWriter.Write((bool)highCq); // InfoEntry.HighCq
+                _infosWriter.Write((bool)false); // InfoEntry.Stubbed
                 _infosWriter.Write((int)ptcInfo.CodeStream.Length); // InfoEntry.CodeLen
                 _infosWriter.Write((int)ptcInfo.RelocEntriesCount); // InfoEntry.RelocEntriesCount
 
@@ -661,12 +744,25 @@ namespace ARMeilleure.Translation.PTC
             return (ulong)HardwareCapabilities.FeatureInfoEdx << 32 | (uint)HardwareCapabilities.FeatureInfoEcx;
         }
 
+        private static uint GetOSPlatform()
+        {
+            uint osPlatform = 0u;
+
+            osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD) ? 1u : 0u) << 0;
+            osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)   ? 1u : 0u) << 1;
+            osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)     ? 1u : 0u) << 2;
+            osPlatform |= (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 1u : 0u) << 3;
+
+            return osPlatform;
+        }
+
         private struct Header
         {
             public string Magic;
 
-            public int CacheFileVersion;
+            public uint CacheFileVersion;
             public ulong FeatureInfo;
+            public uint OSPlatform;
 
             public int InfosLen;
             public int CodesLen;
@@ -676,11 +772,12 @@ namespace ARMeilleure.Translation.PTC
 
         private struct InfoEntry
         {
-            public const int Stride = 25; // Bytes.
+            public const int Stride = 26; // Bytes.
 
             public ulong Address;
             public ulong GuestSize;
             public bool HighCq;
+            public bool Stubbed;
             public int CodeLen;
             public int RelocEntriesCount;
         }
diff --git a/ARMeilleure/Translation/PTC/PtcInfo.cs b/ARMeilleure/Translation/PTC/PtcInfo.cs
index f03eb6ba41..547fe6d8a7 100644
--- a/ARMeilleure/Translation/PTC/PtcInfo.cs
+++ b/ARMeilleure/Translation/PTC/PtcInfo.cs
@@ -4,7 +4,7 @@ using System.IO;
 
 namespace ARMeilleure.Translation.PTC
 {
-    sealed class PtcInfo : IDisposable
+    class PtcInfo : IDisposable
     {
         private readonly BinaryWriter _relocWriter;
         private readonly BinaryWriter _unwindInfoWriter;
diff --git a/ARMeilleure/Translation/PTC/PtcJumpTable.cs b/ARMeilleure/Translation/PTC/PtcJumpTable.cs
index e4af68d68b..d52d08743d 100644
--- a/ARMeilleure/Translation/PTC/PtcJumpTable.cs
+++ b/ARMeilleure/Translation/PTC/PtcJumpTable.cs
@@ -2,15 +2,15 @@ using ARMeilleure.Translation.Cache;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
 using System.Runtime.InteropServices;
 
 namespace ARMeilleure.Translation.PTC
 {
-    [Serializable]
     class PtcJumpTable
     {
-        [Serializable]
-        private struct TableEntry<TAddress>
+        public struct TableEntry<TAddress>
         {
             public int EntryIndex;
             public long GuestAddress;
@@ -24,82 +24,270 @@ namespace ARMeilleure.Translation.PTC
             }
         }
 
-        private enum DirectHostAddress
+        public enum DirectHostAddress
         {
-            CallStub,
-            TailCallStub,
-            Host
+            CallStub = 0,
+            TailCallStub = 1,
+            Host = 2
         }
 
-        private enum IndirectHostAddress
+        public enum IndirectHostAddress
         {
-            CallStub,
-            TailCallStub
+            CallStub = 0,
+            TailCallStub = 1
         }
 
         private readonly List<TableEntry<DirectHostAddress>> _jumpTable;
         private readonly List<TableEntry<IndirectHostAddress>> _dynamicTable;
 
-        private readonly List<ulong> _targets;
-        private readonly Dictionary<ulong, List<int>> _dependants;
-        private readonly Dictionary<ulong, List<int>> _owners;
-
-        public List<ulong> Targets => _targets;
-        public Dictionary<ulong, List<int>> Dependants => _dependants;
-        public Dictionary<ulong, List<int>> Owners => _owners;
+        public List<ulong> Targets { get; }
+        public Dictionary<ulong, List<int>> Dependants { get; }
+        public Dictionary<ulong, List<int>> Owners { get; }
 
         public PtcJumpTable()
         {
             _jumpTable = new List<TableEntry<DirectHostAddress>>();
             _dynamicTable = new List<TableEntry<IndirectHostAddress>>();
 
-            _targets = new List<ulong>();
-            _dependants = new Dictionary<ulong, List<int>>();
-            _owners = new Dictionary<ulong, List<int>>();
+            Targets = new List<ulong>();
+            Dependants = new Dictionary<ulong, List<int>>();
+            Owners = new Dictionary<ulong, List<int>>();
+        }
+
+        public PtcJumpTable(
+            List<TableEntry<DirectHostAddress>> jumpTable, List<TableEntry<IndirectHostAddress>> dynamicTable,
+            List<ulong> targets, Dictionary<ulong, List<int>> dependants, Dictionary<ulong, List<int>> owners)
+        {
+            _jumpTable = jumpTable;
+            _dynamicTable = dynamicTable;
+
+            Targets = targets;
+            Dependants = dependants;
+            Owners = owners;
+        }
+
+        public static PtcJumpTable Deserialize(MemoryStream stream)
+        {
+            using (BinaryReader reader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true))
+            {
+                var jumpTable = new List<TableEntry<DirectHostAddress>>();
+
+                int jumpTableCount = reader.ReadInt32();
+
+                for (int i = 0; i < jumpTableCount; i++)
+                {
+                    int entryIndex = reader.ReadInt32();
+                    long guestAddress = reader.ReadInt64();
+                    DirectHostAddress hostAddress = (DirectHostAddress)reader.ReadInt32();
+
+                    jumpTable.Add(new TableEntry<DirectHostAddress>(entryIndex, guestAddress, hostAddress));
+                }
+
+                var dynamicTable = new List<TableEntry<IndirectHostAddress>>();
+
+                int dynamicTableCount = reader.ReadInt32();
+
+                for (int i = 0; i < dynamicTableCount; i++)
+                {
+                    int entryIndex = reader.ReadInt32();
+                    long guestAddress = reader.ReadInt64();
+                    IndirectHostAddress hostAddress = (IndirectHostAddress)reader.ReadInt32();
+
+                    dynamicTable.Add(new TableEntry<IndirectHostAddress>(entryIndex, guestAddress, hostAddress));
+                }
+
+                var targets = new List<ulong>();
+
+                int targetsCount = reader.ReadInt32();
+
+                for (int i = 0; i < targetsCount; i++)
+                {
+                    ulong address = reader.ReadUInt64();
+
+                    targets.Add(address);
+                }
+
+                var dependants = new Dictionary<ulong, List<int>>();
+
+                int dependantsCount = reader.ReadInt32();
+
+                for (int i = 0; i < dependantsCount; i++)
+                {
+                    ulong address = reader.ReadUInt64();
+
+                    var entries = new List<int>();
+
+                    int entriesCount = reader.ReadInt32();
+
+                    for (int j = 0; j < entriesCount; j++)
+                    {
+                        int entry = reader.ReadInt32();
+
+                        entries.Add(entry);
+                    }
+
+                    dependants.Add(address, entries);
+                }
+
+                var owners = new Dictionary<ulong, List<int>>();
+
+                int ownersCount = reader.ReadInt32();
+
+                for (int i = 0; i < ownersCount; i++)
+                {
+                    ulong address = reader.ReadUInt64();
+
+                    var entries = new List<int>();
+
+                    int entriesCount = reader.ReadInt32();
+
+                    for (int j = 0; j < entriesCount; j++)
+                    {
+                        int entry = reader.ReadInt32();
+
+                        entries.Add(entry);
+                    }
+
+                    owners.Add(address, entries);
+                }
+
+                return new PtcJumpTable(jumpTable, dynamicTable, targets, dependants, owners);
+            }
+        }
+
+        public static void Serialize(MemoryStream stream, PtcJumpTable ptcJumpTable)
+        {
+            using (BinaryWriter writer = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true))
+            {
+                writer.Write((int)ptcJumpTable._jumpTable.Count);
+
+                foreach (var tableEntry in ptcJumpTable._jumpTable)
+                {
+                    writer.Write((int)tableEntry.EntryIndex);
+                    writer.Write((long)tableEntry.GuestAddress);
+                    writer.Write((int)tableEntry.HostAddress);
+                }
+
+                writer.Write((int)ptcJumpTable._dynamicTable.Count);
+
+                foreach (var tableEntry in ptcJumpTable._dynamicTable)
+                {
+                    writer.Write((int)tableEntry.EntryIndex);
+                    writer.Write((long)tableEntry.GuestAddress);
+                    writer.Write((int)tableEntry.HostAddress);
+                }
+
+                writer.Write((int)ptcJumpTable.Targets.Count);
+
+                foreach (ulong address in ptcJumpTable.Targets)
+                {
+                    writer.Write((ulong)address);
+                }
+
+                writer.Write((int)ptcJumpTable.Dependants.Count);
+
+                foreach (var kv in ptcJumpTable.Dependants)
+                {
+                    writer.Write((ulong)kv.Key); // address
+
+                    writer.Write((int)kv.Value.Count);
+
+                    foreach (int entry in kv.Value)
+                    {
+                        writer.Write((int)entry);
+                    }
+                }
+
+                writer.Write((int)ptcJumpTable.Owners.Count);
+
+                foreach (var kv in ptcJumpTable.Owners)
+                {
+                    writer.Write((ulong)kv.Key); // address
+
+                    writer.Write((int)kv.Value.Count);
+
+                    foreach (int entry in kv.Value)
+                    {
+                        writer.Write((int)entry);
+                    }
+                }
+            }
         }
 
         public void Initialize(JumpTable jumpTable)
         {
-            _targets.Clear();
+            Targets.Clear();
 
             foreach (ulong guestAddress in jumpTable.Targets.Keys)
             {
-                _targets.Add(guestAddress);
+                Targets.Add(guestAddress);
             }
 
-            _dependants.Clear();
+            Dependants.Clear();
 
-            foreach (var item in jumpTable.Dependants)
+            foreach (var kv in jumpTable.Dependants)
             {
-                _dependants.Add(item.Key, new List<int>(item.Value));
+                Dependants.Add(kv.Key, new List<int>(kv.Value));
             }
 
-            _owners.Clear();
+            Owners.Clear();
 
-            foreach (var item in jumpTable.Owners)
+            foreach (var kv in jumpTable.Owners)
             {
-                _owners.Add(item.Key, new List<int>(item.Value));
+                Owners.Add(kv.Key, new List<int>(kv.Value));
             }
         }
 
+        // For future use.
+        public void Clean(ulong guestAddress)
+        {
+            if (Owners.TryGetValue(guestAddress, out List<int> entries))
+            {
+                foreach (int entry in entries)
+                {
+                    if ((entry & JumpTable.DynamicEntryTag) == 0)
+                    {
+                        int removed = _jumpTable.RemoveAll(tableEntry => tableEntry.EntryIndex == entry);
+
+                        Debug.Assert(removed == 1);
+                    }
+                    else
+                    {
+                        if (JumpTable.DynamicTableElems > 1)
+                        {
+                            throw new NotSupportedException();
+                        }
+
+                        int removed = _dynamicTable.RemoveAll(tableEntry => tableEntry.EntryIndex == (entry & ~JumpTable.DynamicEntryTag));
+
+                        Debug.Assert(removed == 1);
+                    }
+                }
+            }
+
+            Targets.Remove(guestAddress);
+            Dependants.Remove(guestAddress);
+            Owners.Remove(guestAddress);
+        }
+
         public void Clear()
         {
             _jumpTable.Clear();
             _dynamicTable.Clear();
 
-            _targets.Clear();
-            _dependants.Clear();
-            _owners.Clear();
+            Targets.Clear();
+            Dependants.Clear();
+            Owners.Clear();
         }
 
         public void WriteJumpTable(JumpTable jumpTable, ConcurrentDictionary<ulong, TranslatedFunction> funcs)
         {
-            // Writes internal state to jump table in-memory, after PTC was loaded.
+            // Writes internal state to jump table in-memory, after PtcJumpTable was deserialized.
 
-            foreach (var item in _jumpTable)
+            foreach (var tableEntry in _jumpTable)
             {
-                long guestAddress = item.GuestAddress;
-                DirectHostAddress directHostAddress = item.HostAddress;
+                long guestAddress = tableEntry.GuestAddress;
+                DirectHostAddress directHostAddress = tableEntry.HostAddress;
 
                 long hostAddress;
 
@@ -119,7 +307,12 @@ namespace ARMeilleure.Translation.PTC
                     }
                     else
                     {
-                        throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{(ulong)guestAddress:X16})");
+                        if (!PtcProfiler.ProfiledFuncs.TryGetValue((ulong)guestAddress, out var value) || !value.highCq)
+                        {
+                            throw new KeyNotFoundException($"({nameof(guestAddress)} = 0x{(ulong)guestAddress:X16})");
+                        }
+
+                        hostAddress = 0L;
                     }
                 }
                 else
@@ -127,7 +320,7 @@ namespace ARMeilleure.Translation.PTC
                     throw new InvalidOperationException(nameof(directHostAddress));
                 }
 
-                int entry = item.EntryIndex;
+                int entry = tableEntry.EntryIndex;
 
                 jumpTable.Table.SetEntry(entry);
                 jumpTable.ExpandIfNeededJumpTable(entry);
@@ -141,17 +334,17 @@ namespace ARMeilleure.Translation.PTC
 
         public void WriteDynamicTable(JumpTable jumpTable)
         {
-            // Writes internal state to jump table in-memory, after PTC was loaded.
+            // Writes internal state to jump table in-memory, after PtcJumpTable was deserialized.
 
             if (JumpTable.DynamicTableElems > 1)
             {
                 throw new NotSupportedException();
             }
 
-            foreach (var item in _dynamicTable)
+            foreach (var tableEntry in _dynamicTable)
             {
-                long guestAddress = item.GuestAddress;
-                IndirectHostAddress indirectHostAddress = item.HostAddress;
+                long guestAddress = tableEntry.GuestAddress;
+                IndirectHostAddress indirectHostAddress = tableEntry.HostAddress;
 
                 long hostAddress;
 
@@ -168,7 +361,7 @@ namespace ARMeilleure.Translation.PTC
                     throw new InvalidOperationException(nameof(indirectHostAddress));
                 }
 
-                int entry = item.EntryIndex;
+                int entry = tableEntry.EntryIndex;
 
                 jumpTable.DynTable.SetEntry(entry);
                 jumpTable.ExpandIfNeededDynamicTable(entry);
@@ -182,7 +375,7 @@ namespace ARMeilleure.Translation.PTC
 
         public void ReadJumpTable(JumpTable jumpTable)
         {
-            // Reads in-memory jump table state and store internally for PTC serialization.
+            // Reads in-memory jump table state and store internally for PtcJumpTable serialization.
 
             _jumpTable.Clear();
 
@@ -216,7 +409,7 @@ namespace ARMeilleure.Translation.PTC
 
         public void ReadDynamicTable(JumpTable jumpTable)
         {
-            // Reads in-memory jump table state and store internally for PTC serialization.
+            // Reads in-memory jump table state and store internally for PtcJumpTable serialization.
 
             if (JumpTable.DynamicTableElems > 1)
             {
diff --git a/ARMeilleure/Translation/PTC/PtcProfiler.cs b/ARMeilleure/Translation/PTC/PtcProfiler.cs
index dbb3ed9d10..bc9814eccf 100644
--- a/ARMeilleure/Translation/PTC/PtcProfiler.cs
+++ b/ARMeilleure/Translation/PTC/PtcProfiler.cs
@@ -1,10 +1,11 @@
 using ARMeilleure.State;
+using Ryujinx.Common.Logging;
 using System;
+using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
 using System.IO.Compression;
-using System.Runtime.Serialization.Formatters.Binary;
 using System.Security.Cryptography;
 using System.Threading;
 
@@ -12,12 +13,14 @@ namespace ARMeilleure.Translation.PTC
 {
     public static class PtcProfiler
     {
+        private const string HeaderMagic = "Phd";
+
+        private const uint InternalVersion = 1713; //! Not to be incremented manually for each change to the ARMeilleure project.
+
         private const int SaveInterval = 30; // Seconds.
 
         private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
 
-        private static readonly BinaryFormatter _binaryFormatter;
-
         private static readonly System.Timers.Timer _timer;
 
         private static readonly ManualResetEvent _waitEvent;
@@ -26,17 +29,15 @@ namespace ARMeilleure.Translation.PTC
 
         private static bool _disposed;
 
-        internal static Dictionary<ulong, (ExecutionMode mode, bool highCq)> ProfiledFuncs { get; private set; } //! Not to be modified.
+        internal static Dictionary<ulong, (ExecutionMode mode, bool highCq)> ProfiledFuncs { get; private set; }
 
         internal static bool Enabled { get; private set; }
 
         public static ulong StaticCodeStart { internal get; set; }
-        public static int   StaticCodeSize  { internal get; set; }
+        public static ulong StaticCodeSize  { internal get; set; }
 
         static PtcProfiler()
         {
-            _binaryFormatter = new BinaryFormatter();
-
             _timer = new System.Timers.Timer((double)SaveInterval * 1000d);
             _timer.Elapsed += PreSave;
 
@@ -55,11 +56,11 @@ namespace ARMeilleure.Translation.PTC
         {
             if (IsAddressInStaticCodeRange(address))
             {
+                Debug.Assert(!highCq);
+
                 lock (_lock)
                 {
-                    Debug.Assert(!highCq && !ProfiledFuncs.ContainsKey(address));
-
-                    ProfiledFuncs.TryAdd(address, (mode, highCq));
+                    ProfiledFuncs.TryAdd(address, (mode, highCq: false));
                 }
             }
         }
@@ -68,18 +69,35 @@ namespace ARMeilleure.Translation.PTC
         {
             if (IsAddressInStaticCodeRange(address))
             {
+                Debug.Assert(highCq);
+
                 lock (_lock)
                 {
-                    Debug.Assert(highCq && ProfiledFuncs.ContainsKey(address));
+                    Debug.Assert(ProfiledFuncs.ContainsKey(address));
 
-                    ProfiledFuncs[address] = (mode, highCq);
+                    ProfiledFuncs[address] = (mode, highCq: true);
                 }
             }
         }
 
         internal static bool IsAddressInStaticCodeRange(ulong address)
         {
-            return address >= StaticCodeStart && address < StaticCodeStart + (ulong)StaticCodeSize;
+            return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
+        }
+
+        internal static Dictionary<ulong, (ExecutionMode mode, bool highCq)> GetProfiledFuncsToTranslate(ConcurrentDictionary<ulong, TranslatedFunction> funcs)
+        {
+            var profiledFuncsToTranslate = new Dictionary<ulong, (ExecutionMode mode, bool highCq)>(ProfiledFuncs);
+
+            foreach (ulong address in profiledFuncsToTranslate.Keys)
+            {
+                if (funcs.ContainsKey(address))
+                {
+                    profiledFuncsToTranslate.Remove(address);
+                }
+            }
+
+            return profiledFuncsToTranslate;
         }
 
         internal static void ClearEntries()
@@ -97,21 +115,21 @@ namespace ARMeilleure.Translation.PTC
 
             if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
             {
-                if (!Load(fileNameActual))
+                if (!Load(fileNameActual, false))
                 {
                     if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
                     {
-                        Load(fileNameBackup);
+                        Load(fileNameBackup, true);
                     }
                 }
             }
             else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
             {
-                Load(fileNameBackup);
+                Load(fileNameBackup, true);
             }
         }
 
-        private static bool Load(string fileName)
+        private static bool Load(string fileName, bool isBackup)
         {
             using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
             using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
@@ -147,9 +165,25 @@ namespace ARMeilleure.Translation.PTC
 
                 stream.Seek((long)hashSize, SeekOrigin.Begin);
 
+                Header header = ReadHeader(stream);
+
+                if (header.Magic != HeaderMagic)
+                {
+                    InvalidateCompressedStream(compressedStream);
+
+                    return false;
+                }
+
+                if (header.InfoFileVersion != InternalVersion)
+                {
+                    InvalidateCompressedStream(compressedStream);
+
+                    return false;
+                }
+
                 try
                 {
-                    ProfiledFuncs = (Dictionary<ulong, (ExecutionMode, bool)>)_binaryFormatter.Deserialize(stream);
+                    ProfiledFuncs = Deserialize(stream);
                 }
                 catch
                 {
@@ -159,9 +193,13 @@ namespace ARMeilleure.Translation.PTC
 
                     return false;
                 }
-
-                return true;
             }
+
+            long fileSize = new FileInfo(fileName).Length;
+
+            Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
+
+            return true;
         }
 
         private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
@@ -169,6 +207,42 @@ namespace ARMeilleure.Translation.PTC
             return currentHash.SequenceEqual(expectedHash);
         }
 
+        private static Header ReadHeader(MemoryStream stream)
+        {
+            using (BinaryReader headerReader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true))
+            {
+                Header header = new Header();
+
+                header.Magic = headerReader.ReadString();
+
+                header.InfoFileVersion = headerReader.ReadUInt32();
+
+                return header;
+            }
+        }
+
+        private static Dictionary<ulong, (ExecutionMode, bool)> Deserialize(MemoryStream stream)
+        {
+            using (BinaryReader reader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true))
+            {
+                var profiledFuncs = new Dictionary<ulong, (ExecutionMode, bool)>();
+
+                int profiledFuncsCount = reader.ReadInt32();
+
+                for (int i = 0; i < profiledFuncsCount; i++)
+                {
+                    ulong address = reader.ReadUInt64();
+
+                    ExecutionMode mode = (ExecutionMode)reader.ReadInt32();
+                    bool highCq = reader.ReadBoolean();
+
+                    profiledFuncs.Add(address, (mode, highCq));
+                }
+
+                return profiledFuncs;
+            }
+        }
+
         private static void InvalidateCompressedStream(FileStream compressedStream)
         {
             compressedStream.SetLength(0L);
@@ -195,6 +269,8 @@ namespace ARMeilleure.Translation.PTC
 
         private static void Save(string fileName)
         {
+            int profiledFuncsCount;
+
             using (MemoryStream stream = new MemoryStream())
             using (MD5 md5 = MD5.Create())
             {
@@ -202,9 +278,13 @@ namespace ARMeilleure.Translation.PTC
 
                 stream.Seek((long)hashSize, SeekOrigin.Begin);
 
+                WriteHeader(stream);
+
                 lock (_lock)
                 {
-                    _binaryFormatter.Serialize(stream, ProfiledFuncs);
+                    Serialize(stream, ProfiledFuncs);
+
+                    profiledFuncsCount = ProfiledFuncs.Count;
                 }
 
                 stream.Seek((long)hashSize, SeekOrigin.Begin);
@@ -231,6 +311,43 @@ namespace ARMeilleure.Translation.PTC
                     }
                 }
             }
+
+            long fileSize = new FileInfo(fileName).Length;
+
+            Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
+        }
+
+        private static void WriteHeader(MemoryStream stream)
+        {
+            using (BinaryWriter headerWriter = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true))
+            {
+                headerWriter.Write((string)HeaderMagic); // Header.Magic
+
+                headerWriter.Write((uint)InternalVersion); // Header.InfoFileVersion
+            }
+        }
+
+        private static void Serialize(MemoryStream stream, Dictionary<ulong, (ExecutionMode mode, bool highCq)> profiledFuncs)
+        {
+            using (BinaryWriter writer = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true))
+            {
+                writer.Write((int)profiledFuncs.Count);
+
+                foreach (var kv in profiledFuncs)
+                {
+                    writer.Write((ulong)kv.Key); // address
+
+                    writer.Write((int)kv.Value.mode);
+                    writer.Write((bool)kv.Value.highCq);
+                }
+            }
+        }
+
+        private struct Header
+        {
+            public string Magic;
+
+            public uint InfoFileVersion;
         }
 
         internal static void Start()
diff --git a/ARMeilleure/Translation/PTC/RelocEntry.cs b/ARMeilleure/Translation/PTC/RelocEntry.cs
index 3d729fbb01..bb77e1f0ff 100644
--- a/ARMeilleure/Translation/PTC/RelocEntry.cs
+++ b/ARMeilleure/Translation/PTC/RelocEntry.cs
@@ -2,6 +2,8 @@ namespace ARMeilleure.Translation.PTC
 {
     struct RelocEntry
     {
+        public const int Stride = 8; // Bytes.
+
         public int Position;
         public int Index;
 
diff --git a/ARMeilleure/Translation/SsaDeconstruction.cs b/ARMeilleure/Translation/SsaDeconstruction.cs
index c3bcaf8c1c..2e9e3281a0 100644
--- a/ARMeilleure/Translation/SsaDeconstruction.cs
+++ b/ARMeilleure/Translation/SsaDeconstruction.cs
@@ -1,5 +1,4 @@
 using ARMeilleure.IntermediateRepresentation;
-using System.Collections.Generic;
 
 using static ARMeilleure.IntermediateRepresentation.OperandHelper;
 using static ARMeilleure.IntermediateRepresentation.OperationHelper;
diff --git a/ARMeilleure/Translation/Translator.cs b/ARMeilleure/Translation/Translator.cs
index ffcd551c9a..d78f5e2126 100644
--- a/ARMeilleure/Translation/Translator.cs
+++ b/ARMeilleure/Translation/Translator.cs
@@ -31,10 +31,11 @@ namespace ARMeilleure.Translation
         private readonly ReaderWriterLock _backgroundTranslatorLock;
 
         private JumpTable _jumpTable;
+        internal JumpTable JumpTable => _jumpTable;
 
         private volatile int _threadCount;
 
-        // FIXME: Remove this once the init logic of the emulator will be redone
+        // FIXME: Remove this once the init logic of the emulator will be redone.
         public static ManualResetEvent IsReadyForTranslation = new ManualResetEvent(false);
 
         public Translator(IJitMemoryAllocator allocator, IMemoryManager memory)
@@ -100,7 +101,7 @@ namespace ARMeilleure.Translation
 
                 if (Ptc.State == PtcState.Enabled)
                 {
-                    Ptc.LoadTranslations(_funcs, _memory.PageTablePointer, _jumpTable);
+                    Ptc.LoadTranslations(_funcs, _memory, _jumpTable);
                     Ptc.MakeAndSaveTranslations(_funcs, _memory, _jumpTable);
                 }
 
diff --git a/Ryujinx.HLE/HOS/ProgramLoader.cs b/Ryujinx.HLE/HOS/ProgramLoader.cs
index 79f8af7a84..03c3ea51bc 100644
--- a/Ryujinx.HLE/HOS/ProgramLoader.cs
+++ b/Ryujinx.HLE/HOS/ProgramLoader.cs
@@ -170,7 +170,7 @@ namespace Ryujinx.HLE.HOS
             }
 
             PtcProfiler.StaticCodeStart = codeStart;
-            PtcProfiler.StaticCodeSize  = (int)codeSize;
+            PtcProfiler.StaticCodeSize  = (ulong)codeSize;
 
             int codePagesCount = (int)(codeSize / KMemoryManager.PageSize);