diff --git a/scripts/generate_aerolib_binary.py b/scripts/generate_aerolib_binary.py index 26e1350..7ce8a80 100644 --- a/scripts/generate_aerolib_binary.py +++ b/scripts/generate_aerolib_binary.py @@ -3,8 +3,8 @@ #!/usr/bin/env python3 -import struct import hashlib +import struct from base64 import b64encode as base64enc from binascii import unhexlify as uhx from pathlib import Path @@ -21,7 +21,7 @@ def name2nid(name): def generate(): names_path = Path(NAMES) output_path = Path(OUTPUT) - + entries = [] with open(names_path, 'r', encoding='utf-8') as f: for line in f: @@ -29,12 +29,12 @@ def generate(): if name: nid = name2nid(name) entries.append((nid, name)) - + print(f"Found {len(entries)} entries") data = bytearray() data.extend(struct.pack(' SetZeroReturn(ctx); - - [SysAbiExport( - Nid = "TU-d9PfIHPM", - ExportName = "sceKernelUnknownTU", - Target = Generation.Gen4 | Generation.Gen5, - LibraryName = "libKernel")] - public static int KernelUnknownTU(CpuContext ctx) => SetZeroReturn(ctx); - - [SysAbiExport( - Nid = "iWQWrwiSt8A", - ExportName = "sceKernelUnknowniWQW", - Target = Generation.Gen4 | Generation.Gen5, - LibraryName = "libKernel")] - public static int KernelUnknowniWQW(CpuContext ctx) => SetZeroReturn(ctx); - - [SysAbiExport( - Nid = "KuOmgKoqCdY", - ExportName = "sceKernelUnknownKuOmg", - Target = Generation.Gen4 | Generation.Gen5, - LibraryName = "libKernel")] - public static int KernelUnknownKuOmg(CpuContext ctx) => SetZeroReturn(ctx); - - [SysAbiExport( - Nid = "RenI1lL1WFk", - ExportName = "sceKernelUnknownRenI", - Target = Generation.Gen4 | Generation.Gen5, - LibraryName = "libKernel")] - public static int KernelUnknownRenI(CpuContext ctx) => SetZeroReturn(ctx); - - [SysAbiExport( - Nid = "pQGpHYopAIY", - ExportName = "sceKernelUnknownpQGp", - Target = Generation.Gen4 | Generation.Gen5, - LibraryName = "libKernel")] - public static int KernelUnknownpQGp(CpuContext ctx) => SetZeroReturn(ctx); - - [SysAbiExport( - Nid = "Rbvt+5Y2iEw", - ExportName = "sceKernelUnknownRbvt", - Target = Generation.Gen4 | Generation.Gen5, - LibraryName = "libKernel")] - public static int KernelUnknownRbvt(CpuContext ctx) => SetZeroReturn(ctx); - // NOTE: "SHtAad20YYM"/"DIxvoy7Ngvk" are sce::Json::Value::getType/getInteger, and // "3GPpjQdAMTw"/"9rAeANT2tyE"/"tsvEmnenz48" are __cxa_guard_acquire/__cxa_guard_release/ // __cxa_atexit (verified by hashing against scripts/ps5_names.txt). Do not register them // here as kernel mutex functions: the cxa guards are implemented in CxxAbiExports.cs and // shadowing them breaks every C++ static-init guard in the game. - private static int SetZeroReturn(CpuContext ctx) - { - ctx[CpuRegister.Rax] = 0; - return 0; - } } diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 5f0e2ce..a154682 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -110,6 +110,15 @@ public static class KernelMemoryCompatExports private static readonly HashSet _negativeStatCache = new(StringComparer.OrdinalIgnoreCase); private static readonly ConcurrentDictionary _aprFileSizeCache = new(StringComparer.OrdinalIgnoreCase); private static long _nextFileDescriptor = 2; + + internal static int AllocateGuestFileDescriptor() + { + lock (_fdGate) + { + return (int)Interlocked.Increment(ref _nextFileDescriptor); + } + } + private static ulong _nextPhysicalAddress; private static ulong _nextVirtualAddress; private static ulong _mainDirectMemoryPoolBase = UnsetMainDirectMemoryPoolBase; @@ -1555,9 +1564,10 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - var directoryFd = (int)Interlocked.Increment(ref _nextFileDescriptor); + int directoryFd; lock (_fdGate) { + directoryFd = AllocateGuestFileDescriptor(); _openDirectories[directoryFd] = new OpenDirectory { Path = hostPath, @@ -1578,9 +1588,10 @@ public static class KernelMemoryCompatExports stream.Seek(0, SeekOrigin.End); } - var fd = (int)Interlocked.Increment(ref _nextFileDescriptor); + int fd; lock (_fdGate) { + fd = AllocateGuestFileDescriptor(); _openFiles[fd] = stream; } @@ -1953,6 +1964,12 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + if (KernelSocketCompatExports.TryCloseSocketFd(fd)) + { + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + FileStream? stream; lock (_fdGate) { @@ -1996,6 +2013,17 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + if (KernelSocketCompatExports.TryReadSocketFd(ctx, fd, bufferAddress, requested, out var socketBytesRead, out var socketError)) + { + if (socketError != OrbisGen2Result.ORBIS_GEN2_OK) + { + return (int)socketError; + } + + ctx[CpuRegister.Rax] = socketBytesRead; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + FileStream? stream; lock (_fdGate) { @@ -2225,6 +2253,17 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + if (KernelSocketCompatExports.TryWriteSocketFd(ctx, fd, payload, out var socketError)) + { + if (socketError != OrbisGen2Result.ORBIS_GEN2_OK) + { + return (int)socketError; + } + + ctx[CpuRegister.Rax] = unchecked((ulong)requested); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + FileStream? stream; lock (_fdGate) { diff --git a/src/SharpEmu.Libs/Kernel/KernelSocketCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelSocketCompatExports.cs new file mode 100644 index 0000000..45ea32c --- /dev/null +++ b/src/SharpEmu.Libs/Kernel/KernelSocketCompatExports.cs @@ -0,0 +1,558 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Buffers.Binary; +using System.Net; +using System.Net.Sockets; +using System.Text; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Kernel; + +internal static class KernelSocketCompatExports +{ + private sealed class EmulatedSocketState + { + public TcpClient? Client; + public NetworkStream? Stream; + public IPAddress BoundAddress = IPAddress.Any; + public int BoundPort; + public bool Bound; + public bool Connected; + } + + private static readonly object Gate = new(); + private static readonly Dictionary Sockets = new(); + + internal static bool IsEmulatedSocketFd(int fd) + { + lock (Gate) + { + return Sockets.ContainsKey(fd); + } + } + + internal static bool TryCloseSocketFd(int fd) + { + lock (Gate) + { + if (!Sockets.Remove(fd, out var state)) + { + return false; + } + + DisposeEmulatedSocket(state); + return true; + } + } + + internal static bool TryReadSocketFd( + CpuContext ctx, + int fd, + ulong bufferAddress, + int requested, + out ulong bytesRead, + out OrbisGen2Result error) + { + bytesRead = 0; + error = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; + + if (!TryGetEmulatedSocketState(fd, out var state) || + state is null || + !state.Connected || + state.Stream is null) + { + return false; + } + + var socketBuffer = GC.AllocateUninitializedArray(requested); + int socketRead; + try + { + socketRead = state.Stream.Read(socketBuffer, 0, requested); + } + catch (IOException) + { + return false; + } + + if (socketRead > 0 && !ctx.Memory.TryWrite(bufferAddress, socketBuffer.AsSpan(0, socketRead))) + { + error = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + return true; + } + + bytesRead = unchecked((ulong)socketRead); + error = OrbisGen2Result.ORBIS_GEN2_OK; + return true; + } + + internal static bool TryWriteSocketFd( + CpuContext ctx, + int fd, + byte[] payload, + out OrbisGen2Result error) + { + error = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; + + if (!TryGetEmulatedSocketState(fd, out var state) || + state is null || + !state.Connected || + state.Stream is null) + { + return false; + } + + try + { + state.Stream.Write(payload, 0, payload.Length); + state.Stream.Flush(); + } + catch (IOException) + { + return false; + } + + if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NET"), "1", StringComparison.Ordinal)) + { + Console.Out.Write(Encoding.UTF8.GetString(payload)); + Console.Out.Flush(); + } + + error = OrbisGen2Result.ORBIS_GEN2_OK; + return true; + } + + [SysAbiExport( + Nid = "TU-d9PfIHPM", + ExportName = "socket", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int Socket(CpuContext ctx) + { + var fd = KernelMemoryCompatExports.AllocateGuestFileDescriptor(); + lock (Gate) + { + Sockets[fd] = new EmulatedSocketState(); + } + + ctx[CpuRegister.Rax] = unchecked((ulong)fd); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "XVL8So3QJUk", + ExportName = "connect", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int Connect(CpuContext ctx) + { + var fd = unchecked((int)ctx[CpuRegister.Rdi]); + var sockaddrAddress = ctx[CpuRegister.Rsi]; + var addrlen = unchecked((int)ctx[CpuRegister.Rdx]); + + if (!TryGetEmulatedSocketState(fd, out _)) + { + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + if (!TryParseGuestSockaddrIn(sockaddrAddress, addrlen, ctx, out var ipAddress, out var port)) + { + LogNet($"connect sockaddr parse failed: fd={fd} addr=0x{sockaddrAddress:X} len={addrlen}"); + RemoveEmulatedSocketFd(fd); + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + var redirectApplied = TryApplyNetRedirect(ref ipAddress); + if (redirectApplied) + { + LogNet($"connect redirect: fd={fd} ip={ipAddress} port={port}"); + } + + if (!IsGuestTcpOutboundAllowed(ipAddress, redirectApplied)) + { + LogNet($"connect denied by outbound policy: fd={fd} ip={ipAddress} port={port}"); + RemoveEmulatedSocketFd(fd); + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + if (!TryEstablishHostTcpConnection(ipAddress, port, out var client, out var stream)) + { + LogNet($"connect failed: fd={fd} ip={ipAddress} port={port}"); + RemoveEmulatedSocketFd(fd); + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + LogNet($"connect ok: fd={fd} ip={ipAddress} port={port}"); + + lock (Gate) + { + if (!Sockets.TryGetValue(fd, out var state) || state is null) + { + try { stream.Dispose(); } catch (IOException) { } + try { client.Dispose(); } catch (IOException) { } + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + DisposeEmulatedSocket(state); + state.Client = client; + state.Stream = stream; + state.Connected = true; + state.BoundAddress = ipAddress; + state.BoundPort = port; + state.Bound = true; + } + + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "KuOmgKoqCdY", + ExportName = "bind", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int Bind(CpuContext ctx) + { + var fd = unchecked((int)ctx[CpuRegister.Rdi]); + var sockaddrAddress = ctx[CpuRegister.Rsi]; + var addrlen = unchecked((int)ctx[CpuRegister.Rdx]); + + if (!TryGetEmulatedSocketState(fd, out var state) || state is null) + { + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + if (!TryParseGuestSockaddrIn(sockaddrAddress, addrlen, ctx, out var ipAddress, out var port)) + { + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + state.BoundAddress = ipAddress; + state.BoundPort = port; + state.Bound = true; + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "RenI1lL1WFk", + ExportName = "getsockname", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int Getsockname(CpuContext ctx) + { + var fd = unchecked((int)ctx[CpuRegister.Rdi]); + var sockaddrAddress = ctx[CpuRegister.Rsi]; + var addrlenAddress = ctx[CpuRegister.Rdx]; + + if (!TryGetEmulatedSocketState(fd, out var state) || state is null || !state.Bound) + { + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + Span addrlenBuffer = stackalloc byte[4]; + if (!ctx.Memory.TryRead(addrlenAddress, addrlenBuffer)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + var addrlen = BinaryPrimitives.ReadInt32LittleEndian(addrlenBuffer); + if (addrlen < 8) + { + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + Span sockaddr = stackalloc byte[16]; + sockaddr[0] = 16; + sockaddr[1] = 2; + BinaryPrimitives.WriteUInt16BigEndian(sockaddr.Slice(2, 2), (ushort)state.BoundPort); + var addressBytes = state.BoundAddress.GetAddressBytes(); + if (addressBytes.Length == 4) + { + addressBytes.CopyTo(sockaddr.Slice(4, 4)); + } + + var writeLength = Math.Min(addrlen, 16); + if (!ctx.Memory.TryWrite(sockaddrAddress, sockaddr.Slice(0, writeLength))) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + BinaryPrimitives.WriteInt32LittleEndian(addrlenBuffer, writeLength); + if (!ctx.Memory.TryWrite(addrlenAddress, addrlenBuffer)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "9oiX1kyeedA", + ExportName = "bzero", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int Bzero(CpuContext ctx) + { + var address = ctx[CpuRegister.Rdi]; + var length = unchecked((int)ctx[CpuRegister.Rsi]); + if (length > 0 && address != 0) + { + var zeros = new byte[length]; + if (!ctx.Memory.TryWrite(address, zeros)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + } + + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "4n51s0zEf0c", + ExportName = "inet_pton", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int InetPton(CpuContext ctx) + { + var af = unchecked((int)ctx[CpuRegister.Rdi]); + var srcAddress = ctx[CpuRegister.Rsi]; + var dstAddress = ctx[CpuRegister.Rdx]; + if (af != 2 || srcAddress == 0 || dstAddress == 0) + { + ctx[CpuRegister.Rax] = unchecked((ulong)0xFFFFFFFFFFFFFFFF); + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + if (!TryReadCString(srcAddress, ctx, out var text) || + !TryParseIpv4Address(text, out var octets)) + { + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + Span packed = stackalloc byte[4]; + packed[0] = octets[0]; + packed[1] = octets[1]; + packed[2] = octets[2]; + packed[3] = octets[3]; + if (!ctx.Memory.TryWrite(dstAddress, packed)) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + + ctx[CpuRegister.Rax] = 1; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "jogUIsOV3-U", + ExportName = "htons", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int Htons(CpuContext ctx) + { + var value = unchecked((ushort)ctx[CpuRegister.Rdi]); + var swapped = (ushort)(((value & 0x00FF) << 8) | ((value >> 8) & 0x00FF)); + ctx[CpuRegister.Rax] = swapped; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + private static bool TryGetEmulatedSocketState(int fd, out EmulatedSocketState? state) + { + lock (Gate) + { + return Sockets.TryGetValue(fd, out state); + } + } + + private static bool TryParseGuestSockaddrIn( + ulong address, + int addrlen, + CpuContext ctx, + out IPAddress ipAddress, + out int port) + { + ipAddress = IPAddress.None; + port = 0; + if (address == 0 || addrlen < 8) + { + return false; + } + + Span buffer = stackalloc byte[16]; + var readLength = Math.Min(addrlen, buffer.Length); + if (!ctx.Memory.TryRead(address, buffer.Slice(0, readLength))) + { + return false; + } + + if (buffer[1] != 2) + { + return false; + } + + port = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(2, 2)); + ipAddress = new IPAddress(buffer.Slice(4, 4).ToArray()); + return true; + } + + private static void DisposeEmulatedSocket(EmulatedSocketState state) + { + try { state.Stream?.Dispose(); } catch (IOException) { } + try { state.Client?.Dispose(); } catch (IOException) { } + state.Stream = null; + state.Client = null; + state.Connected = false; + } + + private static void RemoveEmulatedSocketFd(int fd) + { + lock (Gate) + { + if (Sockets.Remove(fd, out var socketState)) + { + DisposeEmulatedSocket(socketState); + } + } + } + + private static void LogNet(string message) + { + if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NET"), "1", StringComparison.Ordinal)) + { + Console.Error.WriteLine($"[LOADER][DEBUG] {message}"); + } + } + + private static bool TryApplyNetRedirect(ref IPAddress ipAddress) + { + var redirect = Environment.GetEnvironmentVariable("SHARPEMU_NET_REDIRECT"); + if (string.IsNullOrWhiteSpace(redirect)) + { + return false; + } + + if (!IPAddress.TryParse(redirect.Trim(), out var redirectAddress)) + { + return false; + } + + ipAddress = redirectAddress; + return true; + } + + private static bool IsNetRedirectConfigured() + { + var redirect = Environment.GetEnvironmentVariable("SHARPEMU_NET_REDIRECT"); + return !string.IsNullOrWhiteSpace(redirect); + } + + private static bool IsGuestTcpOutboundAllowed(IPAddress ipAddress, bool redirectApplied) + { + return redirectApplied || IsNetRedirectConfigured() || IPAddress.IsLoopback(ipAddress); + } + + private static bool TryEstablishHostTcpConnection( + IPAddress ipAddress, + int port, + out TcpClient client, + out NetworkStream stream) + { + client = null!; + stream = null!; + if (!TryConnectTcpClient(ipAddress, port, out client)) + { + return false; + } + + stream = client.GetStream(); + return true; + } + + private static bool TryConnectTcpClient(IPAddress ipAddress, int port, out TcpClient client) + { + client = new TcpClient(); + try + { + var connectTask = client.ConnectAsync(ipAddress, port); + if (!connectTask.Wait(TimeSpan.FromMilliseconds(500))) + { + client.Dispose(); + client = null!; + return false; + } + + return true; + } + catch (SocketException) + { + client.Dispose(); + client = null!; + return false; + } + catch (IOException) + { + client.Dispose(); + client = null!; + return false; + } + } + + private static bool TryReadCString(ulong address, CpuContext ctx, out string text) + { + const int maxLength = 64; + var buffer = new byte[maxLength]; + var length = 0; + for (; length < maxLength; length++) + { + if (!ctx.Memory.TryRead(address + (ulong)length, buffer.AsSpan(length, 1))) + { + text = string.Empty; + return false; + } + + if (buffer[length] == 0) + { + break; + } + } + + text = Encoding.ASCII.GetString(buffer, 0, length); + return true; + } + + private static bool TryParseIpv4Address(string text, out byte[] octets) + { + octets = Array.Empty(); + var parts = text.Split('.', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (parts.Length != 4) + { + return false; + } + + var parsed = new byte[4]; + for (var i = 0; i < 4; i++) + { + if (!byte.TryParse(parts[i], out parsed[i])) + { + return false; + } + } + + octets = parsed; + return true; + } +} diff --git a/src/SharpEmu.Libs/Network/NetExports.cs b/src/SharpEmu.Libs/Network/NetExports.cs index 616a6db..65b2dc3 100644 --- a/src/SharpEmu.Libs/Network/NetExports.cs +++ b/src/SharpEmu.Libs/Network/NetExports.cs @@ -95,6 +95,54 @@ public static class NetExports return ctx.SetReturn(0); } + [SysAbiExport( + Nid = "9T2pDF2Ryqg", + ExportName = "sceNetHtonl", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetHtonl(CpuContext ctx) + { + var value = unchecked((uint)ctx[CpuRegister.Rdi]); + ctx[CpuRegister.Rax] = BinaryPrimitives.ReverseEndianness(value); + return ctx.SetReturn(0); + } + + [SysAbiExport( + Nid = "iWQWrwiSt8A", + ExportName = "sceNetHtons", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetHtons(CpuContext ctx) + { + var value = unchecked((ushort)ctx[CpuRegister.Rdi]); + ctx[CpuRegister.Rax] = BinaryPrimitives.ReverseEndianness(value); + return ctx.SetReturn(0); + } + + [SysAbiExport( + Nid = "pQGpHYopAIY", + ExportName = "sceNetNtohl", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetNtohl(CpuContext ctx) + { + var value = unchecked((uint)ctx[CpuRegister.Rdi]); + ctx[CpuRegister.Rax] = BinaryPrimitives.ReverseEndianness(value); + return ctx.SetReturn(0); + } + + [SysAbiExport( + Nid = "Rbvt+5Y2iEw", + ExportName = "sceNetNtohs", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetNtohs(CpuContext ctx) + { + var value = unchecked((ushort)ctx[CpuRegister.Rdi]); + ctx[CpuRegister.Rax] = BinaryPrimitives.ReverseEndianness(value); + return ctx.SetReturn(0); + } + [SysAbiExport( Nid = "C4UgDHHPvdw", ExportName = "sceNetResolverCreate",