// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.HLE.FileSystem;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Threading;
namespace SharpEmu.Libs.Kernel;
///
/// Positional and scatter/gather file I/O, synchronization, renaming, fcntl,
/// polling and asynchronous I/O (AIO) that complement the base file exports.
/// These share the same guest FD table and path-resolution helpers via the
/// partial class.
///
public static partial class KernelMemoryCompatExports
{
// fcntl commands (FreeBSD numbering used by the PS4/PS5 libc).
private const int F_DUPFD = 0;
private const int F_GETFD = 1;
private const int F_SETFD = 2;
private const int F_GETFL = 3;
private const int F_SETFL = 4;
// poll event bits.
private const short POLLIN = 0x0001;
private const short POLLOUT = 0x0004;
// AIO request state (SCE_KERNEL_AIO_STATE_*).
private const uint AioStateCompleted = 3;
private const int SizeofAioRwRequest = 40;
private static long _nextAioSubmitId = 1;
private static readonly ConcurrentDictionary _aioResults = new();
private static Stream? GetOpenFile(int fd)
{
lock (_fdGate)
{
return _openFiles.TryGetValue(fd, out var stream) ? stream : null;
}
}
// A descriptor is a host file or a read-only file served from a mounted archive.
private static int ReadOpenFileAt(Stream stream, Span buffer, long offset) => stream switch
{
FileStream file => RandomAccess.Read(file.SafeFileHandle, buffer, offset),
ZArchiveFileStream archived => archived.ReadAt(buffer, offset),
_ => throw new IOException("The descriptor does not support positional reads."),
};
private static void WriteOpenFileAt(Stream stream, ReadOnlySpan payload, long offset)
{
if (stream is not FileStream file)
{
throw new IOException("The descriptor is read-only.");
}
RandomAccess.Write(file.SafeFileHandle, payload, offset);
}
private static void FlushOpenFileToDisk(Stream stream)
{
if (stream is FileStream file)
{
file.Flush(flushToDisk: true);
}
}
// ---- Positional read/write (do not move the file offset) ----
[SysAbiExport(Nid = "ezv-RSBNKqI", ExportName = "pread",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixPread(CpuContext ctx) => KernelPreadCore(ctx);
[SysAbiExport(Nid = "+r3rMFwItV4", ExportName = "sceKernelPread",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelPread(CpuContext ctx) => KernelPreadCore(ctx);
private static int KernelPreadCore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var bufferAddress = ctx[CpuRegister.Rsi];
var requested = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
var offset = unchecked((long)ctx[CpuRegister.Rcx]);
if (requested < 0 || (requested > 0 && bufferAddress == 0) || offset < 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var stream = GetOpenFile(fd);
if (stream is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (requested == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var buffer = GC.AllocateUninitializedArray(requested);
int read;
try
{
read = ReadOpenFileAt(stream, buffer, offset);
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (read > 0 && !ctx.Memory.TryWrite(bufferAddress, buffer.AsSpan(0, read)))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)read);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(Nid = "C2kJ-byS5rM", ExportName = "pwrite",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixPwrite(CpuContext ctx) => KernelPwriteCore(ctx);
[SysAbiExport(Nid = "nKWi-N2HBV4", ExportName = "sceKernelPwrite",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelPwrite(CpuContext ctx) => KernelPwriteCore(ctx);
private static int KernelPwriteCore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var bufferAddress = ctx[CpuRegister.Rsi];
var requested = (int)Math.Min(ctx[CpuRegister.Rdx], int.MaxValue);
var offset = unchecked((long)ctx[CpuRegister.Rcx]);
if (requested < 0 || (requested > 0 && bufferAddress == 0) || offset < 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var stream = GetOpenFile(fd);
if (stream is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (requested == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
var payload = GC.AllocateUninitializedArray(requested);
if (!ctx.Memory.TryRead(bufferAddress, payload))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
try
{
WriteOpenFileAt(stream, payload, offset);
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ctx[CpuRegister.Rax] = unchecked((ulong)requested);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// ---- Synchronization ----
[SysAbiExport(Nid = "juWbTNM+8hw", ExportName = "fsync",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixFsync(CpuContext ctx) => KernelFsyncCore(ctx);
[SysAbiExport(Nid = "fTx66l5iWIA", ExportName = "sceKernelFsync",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelFsync(CpuContext ctx) => KernelFsyncCore(ctx);
[SysAbiExport(Nid = "KIbJFQ0I1Cg", ExportName = "fdatasync",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixFdatasync(CpuContext ctx) => KernelFsyncCore(ctx);
[SysAbiExport(Nid = "30Rh4ixbKy4", ExportName = "sceKernelFdatasync",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelFdatasync(CpuContext ctx) => KernelFsyncCore(ctx);
private static int KernelFsyncCore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var stream = GetOpenFile(fd);
if (stream is null)
{
if (fd is 0 or 1 or 2)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
try
{
FlushOpenFileToDisk(stream);
}
catch (IOException)
{
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(Nid = "Y2OqwJQ3lr8", ExportName = "sync",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixSync(CpuContext ctx)
{
lock (_fdGate)
{
foreach (var stream in _openFiles.Values)
{
try { FlushOpenFileToDisk(stream); } catch (IOException) { }
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// ---- Truncation ----
[SysAbiExport(Nid = "ih4CD9-gghM", ExportName = "ftruncate",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixFtruncate(CpuContext ctx) => KernelFtruncateCore(ctx);
[SysAbiExport(Nid = "VW3TVZiM4-E", ExportName = "sceKernelFtruncate",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelFtruncate(CpuContext ctx) => KernelFtruncateCore(ctx);
private static int KernelFtruncateCore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var length = unchecked((long)ctx[CpuRegister.Rsi]);
if (length < 0)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var stream = GetOpenFile(fd);
if (stream is null)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
try
{
stream.SetLength(length);
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(Nid = "ayrtszI7GBg", ExportName = "truncate",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixTruncate(CpuContext ctx) => KernelTruncateCore(ctx);
[SysAbiExport(Nid = "WlyEA-sLDf0", ExportName = "sceKernelTruncate",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelTruncate(CpuContext ctx) => KernelTruncateCore(ctx);
private static int KernelTruncateCore(CpuContext ctx)
{
var pathAddress = ctx[CpuRegister.Rdi];
var length = unchecked((long)ctx[CpuRegister.Rsi]);
if (length < 0 || !TryReadNullTerminatedUtf8(ctx, pathAddress, MaxGuestStringLength, out var guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
if (IsReadOnlyGuestMutationPath(guestPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
var hostPath = ResolveGuestPath(guestPath);
if (string.IsNullOrEmpty(hostPath))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
try
{
using var stream = new FileStream(hostPath, FileMode.Open, FileAccess.Write, GuestFileShare);
stream.SetLength(length);
}
catch (FileNotFoundException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// ---- Rename ----
[SysAbiExport(Nid = "NN01qLRhiqU", ExportName = "rename",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixRename(CpuContext ctx) => KernelRenameCore(ctx);
[SysAbiExport(Nid = "52NcYU9+lEo", ExportName = "sceKernelRename",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelRename(CpuContext ctx) => KernelRenameCore(ctx);
private static int KernelRenameCore(CpuContext ctx)
{
if (!TryReadNullTerminatedUtf8(ctx, ctx[CpuRegister.Rdi], MaxGuestStringLength, out var fromGuest) ||
!TryReadNullTerminatedUtf8(ctx, ctx[CpuRegister.Rsi], MaxGuestStringLength, out var toGuest))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
if (IsReadOnlyGuestMutationPath(fromGuest) || IsReadOnlyGuestMutationPath(toGuest))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_PERMISSION_DENIED;
}
var fromHost = ResolveGuestPath(fromGuest);
var toHost = ResolveGuestPath(toGuest);
if (string.IsNullOrEmpty(fromHost) || string.IsNullOrEmpty(toHost))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
try
{
if (Directory.Exists(fromHost))
{
Directory.Move(fromHost, toHost);
}
else
{
File.Move(fromHost, toHost, overwrite: true);
}
}
catch (FileNotFoundException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
catch (DirectoryNotFoundException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
catch (IOException)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
InvalidateNegativeStatCacheForPathAndAncestors(toGuest);
AddNegativeStatCacheForGuestPath(fromGuest);
InvalidateAprFileSizeCache(fromHost);
InvalidateAprFileSizeCache(toHost);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// ---- Descriptor duplication ----
[SysAbiExport(Nid = "iiQjzvfWDq0", ExportName = "dup",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixDup(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
lock (_fdGate)
{
if (!_openFiles.TryGetValue(fd, out var stream))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
// POSIX dup shares the open file description (and offset), which is
// exactly the shared FileStream reference.
var newFd = (int)Interlocked.Increment(ref _nextFileDescriptor);
_openFiles[newFd] = stream;
ctx[CpuRegister.Rax] = unchecked((ulong)newFd);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(Nid = "wdUufa9g-D8", ExportName = "dup2",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixDup2(CpuContext ctx)
{
var oldFd = unchecked((int)ctx[CpuRegister.Rdi]);
var newFd = unchecked((int)ctx[CpuRegister.Rsi]);
lock (_fdGate)
{
if (!_openFiles.TryGetValue(oldFd, out var stream))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
if (oldFd == newFd)
{
ctx[CpuRegister.Rax] = unchecked((ulong)newFd);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// If newFd names an open file, dup2 closes it first.
if (_openFiles.TryGetValue(newFd, out var existing) && !ReferenceEquals(existing, stream))
{
try { existing.Dispose(); } catch (IOException) { }
}
_openFiles[newFd] = stream;
ctx[CpuRegister.Rax] = unchecked((ulong)newFd);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// ---- fcntl ----
[SysAbiExport(Nid = "8nY19bKoiZk", ExportName = "fcntl",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixFcntl(CpuContext ctx) => KernelFcntlCore(ctx);
[SysAbiExport(Nid = "SoZkxZkCHaw", ExportName = "sceKernelFcntl",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelFcntl(CpuContext ctx) => KernelFcntlCore(ctx);
private static int KernelFcntlCore(CpuContext ctx)
{
var fd = unchecked((int)ctx[CpuRegister.Rdi]);
var command = unchecked((int)ctx[CpuRegister.Rsi]);
var argument = unchecked((int)ctx[CpuRegister.Rdx]);
switch (command)
{
case F_DUPFD:
lock (_fdGate)
{
if (!_openFiles.TryGetValue(fd, out var stream))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND;
}
var newFd = Math.Max((int)Interlocked.Increment(ref _nextFileDescriptor), argument);
_openFiles[newFd] = stream;
ctx[CpuRegister.Rax] = unchecked((ulong)newFd);
}
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
case F_GETFD:
case F_GETFL:
// No close-on-exec / status flags are tracked; report cleared.
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
case F_SETFD:
case F_SETFL:
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
default:
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}
// ---- poll / select ----
// Regular files are always ready for both read and write, so report every
// requested descriptor as immediately ready.
[SysAbiExport(Nid = "ku7D4q1Y9PI", ExportName = "poll",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixPoll(CpuContext ctx)
{
var fdsAddress = ctx[CpuRegister.Rdi];
var count = unchecked((uint)ctx[CpuRegister.Rsi]);
if (fdsAddress == 0 || count == 0)
{
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// struct pollfd { int fd; short events; short revents; } == 8 bytes.
var ready = 0;
Span buffer = stackalloc byte[8];
for (uint i = 0; i < count && i < 4096; i++)
{
var entry = fdsAddress + i * 8;
if (!ctx.Memory.TryRead(entry, buffer))
{
break;
}
var events = BinaryPrimitives.ReadInt16LittleEndian(buffer[4..]);
var revents = (short)(events & (POLLIN | POLLOUT));
BinaryPrimitives.WriteInt16LittleEndian(buffer[6..], revents);
if (revents != 0)
{
ready++;
}
_ = ctx.Memory.TryWrite(entry, buffer);
}
ctx[CpuRegister.Rax] = unchecked((ulong)ready);
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(Nid = "T8fER+tIGgk", ExportName = "select",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int PosixSelect(CpuContext ctx)
{
// nfds in Rdi; the fd_sets are left as-is (all reported ready) and the
// ready count returned is nfds so callers proceed without blocking.
var nfds = unchecked((int)ctx[CpuRegister.Rdi]);
ctx[CpuRegister.Rax] = unchecked((ulong)Math.Max(nfds, 0));
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
// ---- Asynchronous I/O (executed synchronously) ----
[SysAbiExport(Nid = "HgX7+AORI58", ExportName = "sceKernelAioSubmitReadCommands",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioSubmitReadCommands(CpuContext ctx) => KernelAioSubmit(ctx, write: false);
[SysAbiExport(Nid = "lXT0m3P-vs4", ExportName = "sceKernelAioSubmitReadCommandsMultiple",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioSubmitReadCommandsMultiple(CpuContext ctx) => KernelAioSubmit(ctx, write: false);
[SysAbiExport(Nid = "XQ8C8y+de+E", ExportName = "sceKernelAioSubmitWriteCommands",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioSubmitWriteCommands(CpuContext ctx) => KernelAioSubmit(ctx, write: true);
private static int KernelAioSubmit(CpuContext ctx, bool write)
{
var requestsAddress = ctx[CpuRegister.Rdi];
var count = unchecked((int)ctx[CpuRegister.Rsi]);
var outIdAddress = ctx[CpuRegister.Rcx];
if (requestsAddress == 0 || count <= 0 || count > 0x10000)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
// Each SceKernelAioRWRequest: offset(8) nbyte(8) buf(8) result(8) fd(4).
Span request = stackalloc byte[SizeofAioRwRequest];
Span result = stackalloc byte[16];
for (var i = 0; i < count; i++)
{
var entry = requestsAddress + (ulong)(i * SizeofAioRwRequest);
if (!ctx.Memory.TryRead(entry, request))
{
break;
}
var offset = BinaryPrimitives.ReadInt64LittleEndian(request[0..]);
var nbyte = (int)Math.Min(BinaryPrimitives.ReadUInt64LittleEndian(request[8..]), int.MaxValue);
var buf = BinaryPrimitives.ReadUInt64LittleEndian(request[16..]);
var resultPtr = BinaryPrimitives.ReadUInt64LittleEndian(request[24..]);
var fd = BinaryPrimitives.ReadInt32LittleEndian(request[32..]);
long transferred = KernelAioTransfer(ctx, fd, offset, nbyte, buf, write);
if (resultPtr != 0)
{
BinaryPrimitives.WriteInt64LittleEndian(result[0..], transferred);
BinaryPrimitives.WriteUInt32LittleEndian(result[8..], AioStateCompleted);
_ = ctx.Memory.TryWrite(resultPtr, result);
}
}
var submitId = unchecked((uint)Interlocked.Increment(ref _nextAioSubmitId));
_aioResults[submitId] = (int)AioStateCompleted;
if (outIdAddress != 0)
{
Span idBuffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(idBuffer, submitId);
_ = ctx.Memory.TryWrite(outIdAddress, idBuffer);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static long KernelAioTransfer(CpuContext ctx, int fd, long offset, int nbyte, ulong buf, bool write)
{
if (nbyte <= 0 || buf == 0 || offset < 0)
{
return 0;
}
var stream = GetOpenFile(fd);
if (stream is null)
{
return -1;
}
var scratch = GC.AllocateUninitializedArray(nbyte);
try
{
if (write)
{
if (!ctx.Memory.TryRead(buf, scratch))
{
return -1;
}
WriteOpenFileAt(stream, scratch, offset);
return nbyte;
}
var read = ReadOpenFileAt(stream, scratch, offset);
if (read > 0 && !ctx.Memory.TryWrite(buf, scratch.AsSpan(0, read)))
{
return -1;
}
return read;
}
catch (IOException)
{
return -1;
}
}
[SysAbiExport(Nid = "o7O4z3jwKzo", ExportName = "sceKernelAioPollRequests",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioPollRequests(CpuContext ctx) => KernelAioComplete(ctx);
[SysAbiExport(Nid = "2pOuoWoCxdk", ExportName = "sceKernelAioPollRequest",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioPollRequest(CpuContext ctx) => KernelAioGetRequestState(ctx);
[SysAbiExport(Nid = "lgK+oIWkJyA", ExportName = "sceKernelAioWaitRequests",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioWaitRequests(CpuContext ctx)
{
if (ctx[CpuRegister.Rcx] is not (1 or 2))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
return KernelAioComplete(ctx);
}
[SysAbiExport(Nid = "KOF-oJbQVvc", ExportName = "sceKernelAioWaitRequest",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioWaitRequest(CpuContext ctx) => KernelAioGetRequestState(ctx);
private static int KernelAioGetRequestState(CpuContext ctx)
{
var submitId = unchecked((uint)ctx[CpuRegister.Rdi]);
if (!_aioResults.TryGetValue(submitId, out var completedState))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
// Submission finishes the transfer before it publishes the request ID.
Span state = stackalloc byte[sizeof(int)];
BinaryPrimitives.WriteInt32LittleEndian(state, completedState);
if (!ctx.Memory.TryWrite(ctx[CpuRegister.Rsi], state))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
private static int KernelAioComplete(CpuContext ctx)
{
var requestIdsAddress = ctx[CpuRegister.Rdi];
var count = unchecked((int)ctx[CpuRegister.Rsi]);
var statesAddress = ctx[CpuRegister.Rdx];
if (count <= 0 || count > 128)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
var byteCount = (ulong)count * sizeof(uint);
if (requestIdsAddress == 0 || statesAddress == 0 ||
requestIdsAddress > ulong.MaxValue - byteCount || statesAddress > ulong.MaxValue - byteCount)
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
Span state = stackalloc byte[sizeof(uint)];
for (var index = 0; index < count; index++)
{
if (!ctx.Memory.TryRead(requestIdsAddress + (ulong)index * sizeof(uint), state))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
var submitId = BinaryPrimitives.ReadUInt32LittleEndian(state);
if (!_aioResults.TryGetValue(submitId, out var completedState))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
}
BinaryPrimitives.WriteInt32LittleEndian(state, completedState);
if (!ctx.Memory.TryWrite(statesAddress + (ulong)index * sizeof(uint), state))
{
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT;
}
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(Nid = "fR521KIGgb8", ExportName = "sceKernelAioCancelRequest",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioCancelRequest(CpuContext ctx)
{
// Already completed; report the completed state if an out pointer given.
var stateAddress = ctx[CpuRegister.Rsi];
if (stateAddress != 0)
{
Span state = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(state, AioStateCompleted);
_ = ctx.Memory.TryWrite(stateAddress, state);
}
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
[SysAbiExport(Nid = "5TgME6AYty4", ExportName = "sceKernelAioDeleteRequest",
Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libKernel")]
public static int KernelAioDeleteRequest(CpuContext ctx)
{
var submitId = unchecked((uint)ctx[CpuRegister.Rdi]);
_aioResults.TryRemove(submitId, out _);
ctx[CpuRegister.Rax] = 0;
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
}
}