// 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; } /// /// Index of the import this context is currently executing, or -1 when it is /// running guest code. Only maintained while guest profiling is enabled; /// alone cannot answer "what is this thread inside right /// now" because it keeps pointing at the last import stub after the call /// returns. /// public int ActiveImportIndex { get; set; } = -1; public ulong Rflags { get; set; } public ulong FsBase { get; set; } public ulong GsBase { get; set; } /// x87 control word observed at the current guest boundary. public ushort FpuControlWord { get; set; } = 0x037F; /// MXCSR observed at the current guest boundary. 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 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 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 buffer = stackalloc byte[sizeof(ushort)]; BinaryPrimitives.WriteUInt16LittleEndian(buffer, value); return Memory.TryWrite(address, buffer); } public bool TryReadInt32(ulong address, out int value) { Span 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 bytes = stackalloc byte[sizeof(int)]; BinaryPrimitives.WriteInt32LittleEndian(bytes, value); return Memory.TryWrite(address, bytes); } public bool TryReadUInt32(ulong address, out uint value) { Span 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 buffer = stackalloc byte[sizeof(uint)]; BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); return Memory.TryWrite(address, buffer); } public bool TryWriteInt64(ulong address, long value) { Span buffer = stackalloc byte[sizeof(long)]; BinaryPrimitives.WriteInt64LittleEndian(buffer, value); return Memory.TryWrite(address, buffer); } public bool TryReadUInt64(ulong address, out ulong value) { Span 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 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.Shared.Rent(capacity) : null; Span 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.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; } }