Files
sharpemu/src/SharpEmu.HLE/CpuContext.cs
T
Miguel Cruz 864cbb0fa0 [AGC/Vulkan] Extend PS5 runtime and rendering compatibility (#216)
* [Core] Add POSIX native execution and PS5 SELF support

Extend the native backend, guest TLS, fixed-address memory, and loader paths needed by PS5 titles on Windows, Linux, and macOS. Keep workstation GC so high-core-count hosts do not reserve over fixed guest image bases.

* [HLE] Expand PS5 service and media compatibility

Add the kernel, threading, save-data, networking, audio, video-codec, font, dialog, and service exports required by newer PS5 software. Preserve every SysAbi NID currently registered by main while adding the compatibility surface used by ASTRO BOT.

* [AGC/Vulkan] Extend Gen5 shader and presentation support

Expand PM4 handling, Gen5 shader translation, MRT and packed export support, guest image tracking, depth initialization, texture aliasing, and Vulkan presentation. Add the performance overlay and address-filtered diagnostics used to validate ASTRO BOT with original shaders.

* [Core] Align static TLS reservation across hosts

* [Pad] Align primary user ID with UserService

* [Gpu] Preserve runtime scalar buffers across renderer seam

* [AGC] Restore omitted command helper exports

* [Vulkan] Reuse primary views for promoted MRT targets

* [Vulkan] Preserve scratch storage bindings in compute dispatches
2026-07-16 02:02:34 +03:00

346 lines
9.7 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers;
using System.Buffers.Binary;
using System.Text;
namespace SharpEmu.HLE;
public sealed class CpuContext(ICpuMemory memory, Generation generation)
{
private readonly ulong[] _registers = new ulong[16];
private readonly ulong[] _xmmRegisters = new ulong[32];
private readonly ulong[] _ymmUpperRegisters = new ulong[32];
private bool _raxWritten;
public ICpuMemory Memory { get; } = memory ?? throw new ArgumentNullException(nameof(memory));
public Generation TargetGeneration { get; } = generation;
public ulong Rip { get; set; }
public ulong Rflags { get; set; }
public ulong FsBase { get; set; }
public ulong GsBase { get; set; }
/// <summary>x87 control word observed at the current guest boundary.</summary>
public ushort FpuControlWord { get; set; } = 0x037F;
/// <summary>MXCSR observed at the current guest boundary.</summary>
public uint Mxcsr { get; set; } = 0x1F80;
public ulong this[CpuRegister register]
{
get => _registers[(int)register];
set
{
_registers[(int)register] = value;
if (register == CpuRegister.Rax)
{
_raxWritten = true;
}
}
}
public void ClearRaxWriteFlag()
{
_raxWritten = false;
}
public bool WasRaxWritten => _raxWritten;
public void GetXmmRegister(int registerIndex, out ulong low, out ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
low = _xmmRegisters[offset];
high = _xmmRegisters[offset + 1];
}
public void SetXmmRegister(int registerIndex, ulong low, ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
_xmmRegisters[offset] = low;
_xmmRegisters[offset + 1] = high;
}
public void GetYmmUpper(int registerIndex, out ulong low, out ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
low = _ymmUpperRegisters[offset];
high = _ymmUpperRegisters[offset + 1];
}
public void SetYmmUpper(int registerIndex, ulong low, ulong high)
{
if ((uint)registerIndex >= 16)
{
throw new ArgumentOutOfRangeException(nameof(registerIndex));
}
var offset = registerIndex * 2;
_ymmUpperRegisters[offset] = low;
_ymmUpperRegisters[offset + 1] = high;
}
public void ClearYmmUpper(int registerIndex)
{
SetYmmUpper(registerIndex, 0, 0);
}
public void ClearAllYmmUpper()
{
Array.Clear(_ymmUpperRegisters);
}
public void GetYmmRegister(
int registerIndex,
out ulong lowLow,
out ulong lowHigh,
out ulong highLow,
out ulong highHigh)
{
GetXmmRegister(registerIndex, out lowLow, out lowHigh);
GetYmmUpper(registerIndex, out highLow, out highHigh);
}
public void SetYmmRegister(
int registerIndex,
ulong lowLow,
ulong lowHigh,
ulong highLow,
ulong highHigh)
{
SetXmmRegister(registerIndex, lowLow, lowHigh);
SetYmmUpper(registerIndex, highLow, highHigh);
}
public bool TryReadByte(ulong address, out byte value)
{
Span<byte> buffer = stackalloc byte[1];
if (!Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = buffer[0];
return true;
}
public bool TryReadUInt16(ulong address, out ushort value)
{
Span<byte> bytes = stackalloc byte[sizeof(ushort)];
if (!Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt16LittleEndian(bytes);
return true;
}
public bool TryWriteUInt16(ulong address, ushort value)
{
Span<byte> buffer = stackalloc byte[sizeof(ushort)];
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
return Memory.TryWrite(address, buffer);
}
public bool TryReadInt32(ulong address, out int value)
{
Span<byte> bytes = stackalloc byte[sizeof(int)];
if (!Memory.TryRead(address, bytes))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadInt32LittleEndian(bytes);
return true;
}
public bool TryWriteInt32(ulong address, int value, bool checkNil = false)
{
if (checkNil && address == 0)
{
return false;
}
Span<byte> bytes = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(bytes, value);
return Memory.TryWrite(address, bytes);
}
public bool TryReadUInt32(ulong address, out uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
if (!Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt32LittleEndian(buffer);
return true;
}
public bool TryWriteUInt32(ulong address, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
return Memory.TryWrite(address, buffer);
}
public bool TryWriteInt64(ulong address, long value)
{
Span<byte> buffer = stackalloc byte[sizeof(long)];
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
return Memory.TryWrite(address, buffer);
}
public bool TryReadUInt64(ulong address, out ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
if (!Memory.TryRead(address, buffer))
{
value = 0;
return false;
}
value = BinaryPrimitives.ReadUInt64LittleEndian(buffer);
return true;
}
public bool TryWriteUInt64(ulong address, ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
return Memory.TryWrite(address, buffer);
}
public bool TryReadNullTerminatedUtf8(ulong address, int capacity, out string value)
{
value = string.Empty;
if (address == 0 || capacity <= 0)
{
return false;
}
const int StackBufferLength = 512;
const int ReadChunkLength = 128;
var rented = capacity > StackBufferLength ? ArrayPool<byte>.Shared.Rent(capacity) : null;
Span<byte> bytes = rented is null ? stackalloc byte[StackBufferLength] : rented;
try
{
var length = 0;
while (length < capacity)
{
// Bulk-read in bounded chunks rather than the full capacity: the string
// may end just before unmapped memory, and overreading past the
// terminator by more than a chunk could fault where the old
// byte-by-byte loop succeeded.
var chunk = Math.Min(ReadChunkLength, capacity - length);
var span = bytes.Slice(length, chunk);
if (Memory.TryRead(address + (ulong)length, span))
{
var terminator = span.IndexOf((byte)0);
if (terminator >= 0)
{
value = Encoding.UTF8.GetString(bytes[..(length + terminator)]);
return true;
}
length += chunk;
continue;
}
// The chunk touches an unreadable range; fall back to per-byte reads so a
// terminator sitting before the bad byte still yields the string.
for (var i = 0; i < chunk; i++)
{
if (!Memory.TryRead(address + (ulong)(length + i), bytes.Slice(length + i, 1)))
{
return false;
}
if (bytes[length + i] == 0)
{
value = Encoding.UTF8.GetString(bytes[..(length + i)]);
return true;
}
}
length += chunk;
}
value = Encoding.UTF8.GetString(bytes[..capacity]);
return true;
}
finally
{
if (rented is not null)
{
ArrayPool<byte>.Shared.Return(rented);
}
}
}
public bool PushUInt64(ulong value)
{
var rsp = this[CpuRegister.Rsp];
rsp -= sizeof(ulong);
this[CpuRegister.Rsp] = rsp;
return TryWriteUInt64(rsp, value);
}
public bool PopUInt64(out ulong value)
{
var rsp = this[CpuRegister.Rsp];
if (!TryReadUInt64(rsp, out value))
{
return false;
}
this[CpuRegister.Rsp] = rsp + sizeof(ulong);
return true;
}
public int SetReturn(int result, Type? cast = null)
{
var value = cast switch
{
null => (ulong)result,
_ when cast == typeof(long) => (ulong)(long)result,
_ => throw new NotSupportedException(),
};
this[CpuRegister.Rax] = unchecked(value);
return result;
}
public int SetReturn(OrbisGen2Result result)
{
this[CpuRegister.Rax] = unchecked((ulong)(int)result);
return (int)result;
}
}