forked from Mirror/Ryujinx
PPTC meets ExeFS Patching. (#1865)
* PPTC meets ExeFS Patching. * InternalVersion = 1865 * Ready! * Optimized the PtcProfiler Load/Save methods.
This commit is contained in:
parent
a8022ca3a1
commit
57ea3f93a3
7 changed files with 190 additions and 166 deletions
|
@ -12,6 +12,8 @@ namespace ARMeilleure.Memory
|
||||||
T ReadTracked<T>(ulong va) where T : unmanaged;
|
T ReadTracked<T>(ulong va) where T : unmanaged;
|
||||||
void Write<T>(ulong va, T value) where T : unmanaged;
|
void Write<T>(ulong va, T value) where T : unmanaged;
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false);
|
||||||
|
|
||||||
ref T GetRef<T>(ulong va) where T : unmanaged;
|
ref T GetRef<T>(ulong va) where T : unmanaged;
|
||||||
|
|
||||||
bool IsMapped(ulong va);
|
bool IsMapped(ulong va);
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace ARMeilleure.Translation.PTC
|
||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 2190; //! To be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 1866; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
|
@ -50,8 +50,6 @@ namespace ARMeilleure.Translation.PTC
|
||||||
private static MemoryStream _relocsStream;
|
private static MemoryStream _relocsStream;
|
||||||
private static MemoryStream _unwindInfosStream;
|
private static MemoryStream _unwindInfosStream;
|
||||||
|
|
||||||
private static BinaryWriter _infosWriter;
|
|
||||||
|
|
||||||
private static readonly ulong _outerHeaderMagic;
|
private static readonly ulong _outerHeaderMagic;
|
||||||
private static readonly ulong _innerHeaderMagic;
|
private static readonly ulong _innerHeaderMagic;
|
||||||
|
|
||||||
|
@ -153,14 +151,10 @@ namespace ARMeilleure.Translation.PTC
|
||||||
_codesList = new List<byte[]>();
|
_codesList = new List<byte[]>();
|
||||||
_relocsStream = new MemoryStream();
|
_relocsStream = new MemoryStream();
|
||||||
_unwindInfosStream = new MemoryStream();
|
_unwindInfosStream = new MemoryStream();
|
||||||
|
|
||||||
_infosWriter = new BinaryWriter(_infosStream, EncodingCache.UTF8NoBOM, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DisposeCarriers()
|
private static void DisposeCarriers()
|
||||||
{
|
{
|
||||||
_infosWriter.Dispose();
|
|
||||||
|
|
||||||
_infosStream.Dispose();
|
_infosStream.Dispose();
|
||||||
_codesList.Clear();
|
_codesList.Clear();
|
||||||
_relocsStream.Dispose();
|
_relocsStream.Dispose();
|
||||||
|
@ -551,26 +545,52 @@ namespace ARMeilleure.Translation.PTC
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long infosStreamLength = _infosStream.Length;
|
||||||
|
long relocsStreamLength = _relocsStream.Length;
|
||||||
|
long unwindInfosStreamLength = _unwindInfosStream.Length;
|
||||||
|
|
||||||
_infosStream.Seek(0L, SeekOrigin.Begin);
|
_infosStream.Seek(0L, SeekOrigin.Begin);
|
||||||
_relocsStream.Seek(0L, SeekOrigin.Begin);
|
_relocsStream.Seek(0L, SeekOrigin.Begin);
|
||||||
_unwindInfosStream.Seek(0L, SeekOrigin.Begin);
|
_unwindInfosStream.Seek(0L, SeekOrigin.Begin);
|
||||||
|
|
||||||
using (BinaryReader infosReader = new(_infosStream, EncodingCache.UTF8NoBOM, true))
|
|
||||||
using (BinaryReader relocsReader = new(_relocsStream, EncodingCache.UTF8NoBOM, true))
|
using (BinaryReader relocsReader = new(_relocsStream, EncodingCache.UTF8NoBOM, true))
|
||||||
using (BinaryReader unwindInfosReader = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true))
|
using (BinaryReader unwindInfosReader = new(_unwindInfosStream, EncodingCache.UTF8NoBOM, true))
|
||||||
{
|
{
|
||||||
for (int index = 0; index < GetEntriesCount(); index++)
|
for (int index = 0; index < GetEntriesCount(); index++)
|
||||||
{
|
{
|
||||||
InfoEntry infoEntry = ReadInfo(infosReader);
|
InfoEntry infoEntry = DeserializeStructure<InfoEntry>(_infosStream);
|
||||||
|
|
||||||
if (infoEntry.Stubbed)
|
if (infoEntry.Stubbed)
|
||||||
{
|
{
|
||||||
SkipCode(index, infoEntry.CodeLength);
|
SkipCode(index, infoEntry.CodeLength);
|
||||||
SkipReloc(infoEntry.RelocEntriesCount);
|
SkipReloc(infoEntry.RelocEntriesCount);
|
||||||
SkipUnwindInfo(unwindInfosReader);
|
SkipUnwindInfo(unwindInfosReader);
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (infoEntry.HighCq || !PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) || !value.HighCq)
|
|
||||||
|
bool isEntryChanged = infoEntry.Hash != ComputeHash(memory, infoEntry.Address, infoEntry.GuestSize);
|
||||||
|
|
||||||
|
if (isEntryChanged || (!infoEntry.HighCq && PtcProfiler.ProfiledFuncs.TryGetValue(infoEntry.Address, out var value) && value.HighCq))
|
||||||
{
|
{
|
||||||
|
infoEntry.Stubbed = true;
|
||||||
|
infoEntry.CodeLength = 0;
|
||||||
|
UpdateInfo(infoEntry);
|
||||||
|
|
||||||
|
StubCode(index);
|
||||||
|
StubReloc(infoEntry.RelocEntriesCount);
|
||||||
|
StubUnwindInfo(unwindInfosReader);
|
||||||
|
|
||||||
|
if (isEntryChanged)
|
||||||
|
{
|
||||||
|
PtcJumpTable.Clean(infoEntry.Address);
|
||||||
|
|
||||||
|
Logger.Info?.Print(LogClass.Ptc, $"Invalidated translated function (address: 0x{infoEntry.Address:X16})");
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
byte[] code = ReadCode(index, infoEntry.CodeLength);
|
byte[] code = ReadCode(index, infoEntry.CodeLength);
|
||||||
|
|
||||||
Counter<uint> callCounter = null;
|
Counter<uint> callCounter = null;
|
||||||
|
@ -590,24 +610,13 @@ namespace ARMeilleure.Translation.PTC
|
||||||
|
|
||||||
Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
|
Debug.Assert(isAddressUnique, $"The address 0x{infoEntry.Address:X16} is not unique.");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
infoEntry.Stubbed = true;
|
|
||||||
infoEntry.CodeLength = 0;
|
|
||||||
UpdateInfo(infoEntry);
|
|
||||||
|
|
||||||
StubCode(index);
|
|
||||||
StubReloc(infoEntry.RelocEntriesCount);
|
|
||||||
StubUnwindInfo(unwindInfosReader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_infosStream.Position < _infosStream.Length ||
|
if (_infosStream.Length != infosStreamLength || _infosStream.Position != infosStreamLength ||
|
||||||
_relocsStream.Position < _relocsStream.Length ||
|
_relocsStream.Length != relocsStreamLength || _relocsStream.Position != relocsStreamLength ||
|
||||||
_unwindInfosStream.Position < _unwindInfosStream.Length)
|
_unwindInfosStream.Length != unwindInfosStreamLength || _unwindInfosStream.Position != unwindInfosStreamLength)
|
||||||
{
|
{
|
||||||
throw new Exception("Could not reach the end of one or more memory streams.");
|
throw new Exception("The length of a memory stream has changed, or its position has not reached or has exceeded its end.");
|
||||||
}
|
}
|
||||||
|
|
||||||
jumpTable.Initialize(PtcJumpTable, funcs);
|
jumpTable.Initialize(PtcJumpTable, funcs);
|
||||||
|
@ -623,20 +632,6 @@ namespace ARMeilleure.Translation.PTC
|
||||||
return _codesList.Count;
|
return _codesList.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InfoEntry ReadInfo(BinaryReader infosReader)
|
|
||||||
{
|
|
||||||
InfoEntry infoEntry = new InfoEntry();
|
|
||||||
|
|
||||||
infoEntry.Address = infosReader.ReadUInt64();
|
|
||||||
infoEntry.GuestSize = infosReader.ReadUInt64();
|
|
||||||
infoEntry.HighCq = infosReader.ReadBoolean();
|
|
||||||
infoEntry.Stubbed = infosReader.ReadBoolean();
|
|
||||||
infoEntry.CodeLength = infosReader.ReadInt32();
|
|
||||||
infoEntry.RelocEntriesCount = infosReader.ReadInt32();
|
|
||||||
|
|
||||||
return infoEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
private static void SkipCode(int index, int codeLength)
|
private static void SkipCode(int index, int codeLength)
|
||||||
{
|
{
|
||||||
|
@ -764,15 +759,9 @@ namespace ARMeilleure.Translation.PTC
|
||||||
|
|
||||||
private static void UpdateInfo(InfoEntry infoEntry)
|
private static void UpdateInfo(InfoEntry infoEntry)
|
||||||
{
|
{
|
||||||
_infosStream.Seek(-InfoEntry.Stride, SeekOrigin.Current);
|
_infosStream.Seek(-Unsafe.SizeOf<InfoEntry>(), SeekOrigin.Current);
|
||||||
|
|
||||||
// WriteInfo.
|
SerializeStructure(_infosStream, infoEntry);
|
||||||
_infosWriter.Write((ulong)infoEntry.Address);
|
|
||||||
_infosWriter.Write((ulong)infoEntry.GuestSize);
|
|
||||||
_infosWriter.Write((bool)infoEntry.HighCq);
|
|
||||||
_infosWriter.Write((bool)infoEntry.Stubbed);
|
|
||||||
_infosWriter.Write((int)infoEntry.CodeLength);
|
|
||||||
_infosWriter.Write((int)infoEntry.RelocEntriesCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void StubCode(int index)
|
private static void StubCode(int index)
|
||||||
|
@ -844,7 +833,7 @@ namespace ARMeilleure.Translation.PTC
|
||||||
|
|
||||||
Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address));
|
Debug.Assert(PtcProfiler.IsAddressInStaticCodeRange(address));
|
||||||
|
|
||||||
TranslatedFunction func = Translator.Translate(memory, jumpTable, countTable, address, item.mode, item.highCq);
|
TranslatedFunction func = Translator.Translate(memory, jumpTable, countTable, address, item.funcProfile.Mode, item.funcProfile.HighCq);
|
||||||
|
|
||||||
bool isAddressUnique = funcs.TryAdd(address, func);
|
bool isAddressUnique = funcs.TryAdd(address, func);
|
||||||
|
|
||||||
|
@ -919,17 +908,26 @@ namespace ARMeilleure.Translation.PTC
|
||||||
while (!endEvent.WaitOne(refreshRate));
|
while (!endEvent.WaitOne(refreshRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void WriteInfoCodeRelocUnwindInfo(ulong address, ulong guestSize, bool highCq, PtcInfo ptcInfo)
|
internal static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
|
||||||
|
{
|
||||||
|
return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void WriteInfoCodeRelocUnwindInfo(ulong address, ulong guestSize, Hash128 hash, bool highCq, PtcInfo ptcInfo)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
// WriteInfo.
|
InfoEntry infoEntry = new InfoEntry();
|
||||||
_infosWriter.Write((ulong)address); // InfoEntry.Address
|
|
||||||
_infosWriter.Write((ulong)guestSize); // InfoEntry.GuestSize
|
infoEntry.Address = address;
|
||||||
_infosWriter.Write((bool)highCq); // InfoEntry.HighCq
|
infoEntry.GuestSize = guestSize;
|
||||||
_infosWriter.Write((bool)false); // InfoEntry.Stubbed
|
infoEntry.Hash = hash;
|
||||||
_infosWriter.Write((int)ptcInfo.Code.Length); // InfoEntry.CodeLength
|
infoEntry.HighCq = highCq;
|
||||||
_infosWriter.Write((int)ptcInfo.RelocEntriesCount); // InfoEntry.RelocEntriesCount
|
infoEntry.Stubbed = false;
|
||||||
|
infoEntry.CodeLength = ptcInfo.Code.Length;
|
||||||
|
infoEntry.RelocEntriesCount = ptcInfo.RelocEntriesCount;
|
||||||
|
|
||||||
|
SerializeStructure(_infosStream, infoEntry);
|
||||||
|
|
||||||
WriteCode(ptcInfo.Code.AsSpan());
|
WriteCode(ptcInfo.Code.AsSpan());
|
||||||
|
|
||||||
|
@ -946,7 +944,7 @@ namespace ARMeilleure.Translation.PTC
|
||||||
_codesList.Add(code.ToArray());
|
_codesList.Add(code.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool GetEndianness()
|
internal static bool GetEndianness()
|
||||||
{
|
{
|
||||||
return BitConverter.IsLittleEndian;
|
return BitConverter.IsLittleEndian;
|
||||||
}
|
}
|
||||||
|
@ -1032,12 +1030,12 @@ namespace ARMeilleure.Translation.PTC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 42*/)]
|
||||||
private struct InfoEntry
|
private struct InfoEntry
|
||||||
{
|
{
|
||||||
public const int Stride = 26; // Bytes.
|
|
||||||
|
|
||||||
public ulong Address;
|
public ulong Address;
|
||||||
public ulong GuestSize;
|
public ulong GuestSize;
|
||||||
|
public Hash128 Hash;
|
||||||
public bool HighCq;
|
public bool HighCq;
|
||||||
public bool Stubbed;
|
public bool Stubbed;
|
||||||
public int CodeLength;
|
public int CodeLength;
|
||||||
|
|
|
@ -50,7 +50,12 @@ namespace ARMeilleure.Translation.PTC
|
||||||
T structure = default(T);
|
T structure = default(T);
|
||||||
|
|
||||||
Span<T> spanT = MemoryMarshal.CreateSpan(ref structure, 1);
|
Span<T> spanT = MemoryMarshal.CreateSpan(ref structure, 1);
|
||||||
stream.Read(MemoryMarshal.AsBytes(spanT));
|
int bytesCount = stream.Read(MemoryMarshal.AsBytes(spanT));
|
||||||
|
|
||||||
|
if (bytesCount != Unsafe.SizeOf<T>())
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
}
|
||||||
|
|
||||||
return structure;
|
return structure;
|
||||||
}
|
}
|
||||||
|
@ -130,7 +135,12 @@ namespace ARMeilleure.Translation.PTC
|
||||||
|
|
||||||
T[] item = new T[itemLength];
|
T[] item = new T[itemLength];
|
||||||
|
|
||||||
stream.Read(MemoryMarshal.AsBytes(item.AsSpan()));
|
int bytesCount = stream.Read(MemoryMarshal.AsBytes(item.AsSpan()));
|
||||||
|
|
||||||
|
if (bytesCount != itemLength)
|
||||||
|
{
|
||||||
|
throw new EndOfStreamException();
|
||||||
|
}
|
||||||
|
|
||||||
list.Add(item);
|
list.Add(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,6 @@ namespace ARMeilleure.Translation.PTC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For future use.
|
|
||||||
public void Clean(ulong guestAddress)
|
public void Clean(ulong guestAddress)
|
||||||
{
|
{
|
||||||
if (Owners.TryGetValue(guestAddress, out List<int> entries))
|
if (Owners.TryGetValue(guestAddress, out List<int> entries))
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
using static ARMeilleure.Translation.PTC.PtcFormatter;
|
using static ARMeilleure.Translation.PTC.PtcFormatter;
|
||||||
|
@ -16,9 +18,9 @@ namespace ARMeilleure.Translation.PTC
|
||||||
{
|
{
|
||||||
public static class PtcProfiler
|
public static class PtcProfiler
|
||||||
{
|
{
|
||||||
private const string HeaderMagic = "Phd";
|
private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
|
||||||
|
|
||||||
private const uint InternalVersion = 1713; //! Not to be incremented manually for each change to the ARMeilleure project.
|
private const uint InternalVersion = 1866; //! Not to be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const int SaveInterval = 30; // Seconds.
|
private const int SaveInterval = 30; // Seconds.
|
||||||
|
|
||||||
|
@ -26,13 +28,15 @@ namespace ARMeilleure.Translation.PTC
|
||||||
|
|
||||||
private static readonly System.Timers.Timer _timer;
|
private static readonly System.Timers.Timer _timer;
|
||||||
|
|
||||||
|
private static readonly ulong _outerHeaderMagic;
|
||||||
|
|
||||||
private static readonly ManualResetEvent _waitEvent;
|
private static readonly ManualResetEvent _waitEvent;
|
||||||
|
|
||||||
private static readonly object _lock;
|
private static readonly object _lock;
|
||||||
|
|
||||||
private static bool _disposed;
|
private static bool _disposed;
|
||||||
|
|
||||||
private static byte[] _lastHash;
|
private static Hash128 _lastHash;
|
||||||
|
|
||||||
internal static Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
|
internal static Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
|
||||||
|
|
||||||
|
@ -46,6 +50,8 @@ namespace ARMeilleure.Translation.PTC
|
||||||
_timer = new System.Timers.Timer((double)SaveInterval * 1000d);
|
_timer = new System.Timers.Timer((double)SaveInterval * 1000d);
|
||||||
_timer.Elapsed += PreSave;
|
_timer.Elapsed += PreSave;
|
||||||
|
|
||||||
|
_outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
|
||||||
|
|
||||||
_waitEvent = new ManualResetEvent(true);
|
_waitEvent = new ManualResetEvent(true);
|
||||||
|
|
||||||
_lock = new object();
|
_lock = new object();
|
||||||
|
@ -90,17 +96,15 @@ namespace ARMeilleure.Translation.PTC
|
||||||
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
|
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ConcurrentQueue<(ulong address, ExecutionMode mode, bool highCq)> GetProfiledFuncsToTranslate(ConcurrentDictionary<ulong, TranslatedFunction> funcs)
|
internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(ConcurrentDictionary<ulong, TranslatedFunction> funcs)
|
||||||
{
|
{
|
||||||
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, ExecutionMode mode, bool highCq)>();
|
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
|
||||||
|
|
||||||
foreach (var profiledFunc in ProfiledFuncs)
|
foreach (var profiledFunc in ProfiledFuncs)
|
||||||
{
|
{
|
||||||
ulong address = profiledFunc.Key;
|
if (!funcs.ContainsKey(profiledFunc.Key))
|
||||||
|
|
||||||
if (!funcs.ContainsKey(address))
|
|
||||||
{
|
{
|
||||||
profiledFuncsToTranslate.Enqueue((address, profiledFunc.Value.Mode, profiledFunc.Value.HighCq));
|
profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +119,7 @@ namespace ARMeilleure.Translation.PTC
|
||||||
|
|
||||||
internal static void PreLoad()
|
internal static void PreLoad()
|
||||||
{
|
{
|
||||||
_lastHash = Array.Empty<byte>();
|
_lastHash = default;
|
||||||
|
|
||||||
string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
|
string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
|
||||||
string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info");
|
string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info");
|
||||||
|
@ -141,11 +145,43 @@ namespace ARMeilleure.Translation.PTC
|
||||||
|
|
||||||
private static bool Load(string fileName, bool isBackup)
|
private static bool Load(string fileName, bool isBackup)
|
||||||
{
|
{
|
||||||
using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
|
using (FileStream compressedStream = new(fileName, FileMode.Open))
|
||||||
using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
|
using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true))
|
||||||
using (MemoryStream stream = new MemoryStream())
|
|
||||||
using (MD5 md5 = MD5.Create())
|
|
||||||
{
|
{
|
||||||
|
OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream);
|
||||||
|
|
||||||
|
if (!outerHeader.IsHeaderValid())
|
||||||
|
{
|
||||||
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outerHeader.Magic != _outerHeaderMagic)
|
||||||
|
{
|
||||||
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outerHeader.InfoFileVersion != InternalVersion)
|
||||||
|
{
|
||||||
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outerHeader.Endianness != Ptc.GetEndianness())
|
||||||
|
{
|
||||||
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (MemoryStream stream = new MemoryStream())
|
||||||
|
{
|
||||||
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
deflateStream.CopyTo(stream);
|
deflateStream.CopyTo(stream);
|
||||||
|
@ -157,54 +193,27 @@ namespace ARMeilleure.Translation.PTC
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int hashSize = md5.HashSize / 8;
|
Debug.Assert(stream.Position == stream.Length);
|
||||||
|
|
||||||
stream.Seek(0L, SeekOrigin.Begin);
|
stream.Seek(0L, SeekOrigin.Begin);
|
||||||
|
|
||||||
byte[] currentHash = new byte[hashSize];
|
Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
|
||||||
stream.Read(currentHash, 0, hashSize);
|
|
||||||
|
|
||||||
byte[] expectedHash = md5.ComputeHash(stream);
|
Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
|
||||||
|
|
||||||
if (!CompareHash(currentHash, expectedHash))
|
if (actualHash != expectedHash)
|
||||||
{
|
{
|
||||||
InvalidateCompressedStream(compressedStream);
|
InvalidateCompressedStream(compressedStream);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = Deserialize(stream);
|
ProfiledFuncs = Deserialize(stream);
|
||||||
|
|
||||||
|
Debug.Assert(stream.Position == stream.Length);
|
||||||
|
|
||||||
|
_lastHash = actualHash;
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
ProfiledFuncs = new Dictionary<ulong, FuncProfile>();
|
|
||||||
|
|
||||||
InvalidateCompressedStream(compressedStream);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastHash = expectedHash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long fileSize = new FileInfo(fileName).Length;
|
long fileSize = new FileInfo(fileName).Length;
|
||||||
|
@ -214,30 +223,16 @@ namespace ARMeilleure.Translation.PTC
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
|
|
||||||
{
|
|
||||||
return currentHash.SequenceEqual(expectedHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Header ReadHeader(Stream 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, FuncProfile> Deserialize(Stream stream)
|
private static Dictionary<ulong, FuncProfile> Deserialize(Stream stream)
|
||||||
{
|
{
|
||||||
return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
|
return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static 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 static void InvalidateCompressedStream(FileStream compressedStream)
|
||||||
{
|
{
|
||||||
compressedStream.SetLength(0L);
|
compressedStream.SetLength(0L);
|
||||||
|
@ -266,14 +261,20 @@ namespace ARMeilleure.Translation.PTC
|
||||||
{
|
{
|
||||||
int profiledFuncsCount;
|
int profiledFuncsCount;
|
||||||
|
|
||||||
|
OuterHeader outerHeader = new OuterHeader();
|
||||||
|
|
||||||
|
outerHeader.Magic = _outerHeaderMagic;
|
||||||
|
|
||||||
|
outerHeader.InfoFileVersion = InternalVersion;
|
||||||
|
outerHeader.Endianness = Ptc.GetEndianness();
|
||||||
|
|
||||||
|
outerHeader.SetHeaderHash();
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = new MemoryStream())
|
||||||
using (MD5 md5 = MD5.Create())
|
|
||||||
{
|
{
|
||||||
int hashSize = md5.HashSize / 8;
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
stream.Seek((long)hashSize, SeekOrigin.Begin);
|
stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
|
||||||
|
|
||||||
WriteHeader(stream);
|
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
|
@ -282,22 +283,26 @@ namespace ARMeilleure.Translation.PTC
|
||||||
profiledFuncsCount = ProfiledFuncs.Count;
|
profiledFuncsCount = ProfiledFuncs.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.Seek((long)hashSize, SeekOrigin.Begin);
|
Debug.Assert(stream.Position == stream.Length);
|
||||||
byte[] hash = md5.ComputeHash(stream);
|
|
||||||
|
stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
|
||||||
|
Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
|
||||||
|
|
||||||
stream.Seek(0L, SeekOrigin.Begin);
|
stream.Seek(0L, SeekOrigin.Begin);
|
||||||
stream.Write(hash, 0, hashSize);
|
SerializeStructure(stream, hash);
|
||||||
|
|
||||||
if (CompareHash(hash, _lastHash))
|
if (hash == _lastHash)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream compressedStream = new FileStream(fileName, FileMode.OpenOrCreate))
|
using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate))
|
||||||
using (DeflateStream deflateStream = new DeflateStream(compressedStream, SaveCompressionLevel, true))
|
using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
SerializeStructure(compressedStream, outerHeader);
|
||||||
|
|
||||||
stream.WriteTo(deflateStream);
|
stream.WriteTo(deflateStream);
|
||||||
|
|
||||||
_lastHash = hash;
|
_lastHash = hash;
|
||||||
|
@ -306,7 +311,7 @@ namespace ARMeilleure.Translation.PTC
|
||||||
{
|
{
|
||||||
compressedStream.Position = 0L;
|
compressedStream.Position = 0L;
|
||||||
|
|
||||||
_lastHash = Array.Empty<byte>();
|
_lastHash = default;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compressedStream.Position < compressedStream.Length)
|
if (compressedStream.Position < compressedStream.Length)
|
||||||
|
@ -324,26 +329,35 @@ namespace ARMeilleure.Translation.PTC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteHeader(Stream 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(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
|
private static void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
|
||||||
{
|
{
|
||||||
SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
|
SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Header
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 29*/)]
|
||||||
|
private struct OuterHeader
|
||||||
{
|
{
|
||||||
public string Magic;
|
public ulong Magic;
|
||||||
|
|
||||||
public uint InfoFileVersion;
|
public uint InfoFileVersion;
|
||||||
|
|
||||||
|
public bool Endianness;
|
||||||
|
|
||||||
|
public Hash128 HeaderHash;
|
||||||
|
|
||||||
|
public void SetHeaderHash()
|
||||||
|
{
|
||||||
|
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
|
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsHeaderValid()
|
||||||
|
{
|
||||||
|
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
|
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)]
|
||||||
|
|
|
@ -7,6 +7,7 @@ using ARMeilleure.Memory;
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using ARMeilleure.Translation.Cache;
|
using ARMeilleure.Translation.Cache;
|
||||||
using ARMeilleure.Translation.PTC;
|
using ARMeilleure.Translation.PTC;
|
||||||
|
using Ryujinx.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -282,7 +283,9 @@ namespace ARMeilleure.Translation
|
||||||
|
|
||||||
ResetPool(highCq ? 1 : 0);
|
ResetPool(highCq ? 1 : 0);
|
||||||
|
|
||||||
Ptc.WriteInfoCodeRelocUnwindInfo(address, funcSize, highCq, ptcInfo);
|
Hash128 hash = Ptc.ComputeHash(memory, address, funcSize);
|
||||||
|
|
||||||
|
Ptc.WriteInfoCodeRelocUnwindInfo(address, funcSize, hash, highCq, ptcInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TranslatedFunction(func, counter, funcSize, highCq);
|
return new TranslatedFunction(func, counter, funcSize, highCq);
|
||||||
|
|
|
@ -507,20 +507,18 @@ namespace Ryujinx.HLE.HOS
|
||||||
metaData = modLoadResult.Npdm;
|
metaData = modLoadResult.Npdm;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasPatches = _fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
|
_fileSystem.ModLoader.ApplyNsoPatches(TitleId, programs);
|
||||||
|
|
||||||
_contentManager.LoadEntries(_device);
|
_contentManager.LoadEntries(_device);
|
||||||
|
|
||||||
bool usePtc = _device.System.EnablePtc;
|
bool usePtc = _device.System.EnablePtc;
|
||||||
|
|
||||||
// don't use PTC if exefs files have been replaced
|
// Don't use PPTC if ExeFs files have been replaced.
|
||||||
usePtc &= !modLoadResult.Modified;
|
usePtc &= !modLoadResult.Modified;
|
||||||
// don't use PTC if exefs files have been patched
|
|
||||||
usePtc &= !hasPatches;
|
|
||||||
|
|
||||||
if (_device.System.EnablePtc && !usePtc)
|
if (_device.System.EnablePtc && !usePtc)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Ptc, $"Detected exefs modifications. PPTC disabled.");
|
Logger.Warning?.Print(LogClass.Ptc, $"Detected unsupported ExeFs modifications. PPTC disabled.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
|
Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
|
||||||
|
|
Reference in a new issue