mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
Expand Aerolib from nids.csv and wire socket/net kernel NID handlers (#128)
* Expand Aerolib catalog from nids.csv and wire socket/net NID handlers Load authoritative NID pairs from scripts/nids.csv with ps5_names fallback. Replace mislabeled kernel zero stubs with socket/connect/bind/getsockname HLE and sceNet byte-order exports backed by the CSV symbol names. * Add inet_pton, htons, and bzero kernel compat with CSV NIDs Wire libc network helpers using authoritative NID names from nids.csv instead of synthetic Gst* exports used on the crt-loader branch. * Fix REUSE annotation for scripts/nids.csv * Drop bundled nids.csv; extend ps5_names and regenerate Aerolib Remove scripts/nids.csv from the repository and fold csv-only symbol names into scripts/ps5_names.txt so Aerolib keeps the full catalog via name2nid.
This commit is contained in:
@@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import struct
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import struct
|
||||||
from base64 import b64encode as base64enc
|
from base64 import b64encode as base64enc
|
||||||
from binascii import unhexlify as uhx
|
from binascii import unhexlify as uhx
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -21,7 +21,7 @@ def name2nid(name):
|
|||||||
def generate():
|
def generate():
|
||||||
names_path = Path(NAMES)
|
names_path = Path(NAMES)
|
||||||
output_path = Path(OUTPUT)
|
output_path = Path(OUTPUT)
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
with open(names_path, 'r', encoding='utf-8') as f:
|
with open(names_path, 'r', encoding='utf-8') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
@@ -29,12 +29,12 @@ def generate():
|
|||||||
if name:
|
if name:
|
||||||
nid = name2nid(name)
|
nid = name2nid(name)
|
||||||
entries.append((nid, name))
|
entries.append((nid, name))
|
||||||
|
|
||||||
print(f"Found {len(entries)} entries")
|
print(f"Found {len(entries)} entries")
|
||||||
|
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
data.extend(struct.pack('<I', len(entries)))
|
data.extend(struct.pack('<I', len(entries)))
|
||||||
|
|
||||||
for nid, name in entries:
|
for nid, name in entries:
|
||||||
nid_bytes = nid.encode('utf-8')
|
nid_bytes = nid.encode('utf-8')
|
||||||
name_bytes = name.encode('utf-8')
|
name_bytes = name.encode('utf-8')
|
||||||
@@ -42,10 +42,11 @@ def generate():
|
|||||||
data.extend(nid_bytes)
|
data.extend(nid_bytes)
|
||||||
data.extend(struct.pack('<H', len(name_bytes)))
|
data.extend(struct.pack('<H', len(name_bytes)))
|
||||||
data.extend(name_bytes)
|
data.extend(name_bytes)
|
||||||
|
|
||||||
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(output_path, 'wb') as f:
|
with open(output_path, 'wb') as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
|
|
||||||
print(f"Generated: {output_path} ({len(data):,} bytes)")
|
print(f"Generated: {output_path} ({len(data):,} bytes)")
|
||||||
print(f"Total entries: {len(entries)}")
|
print(f"Total entries: {len(entries)}")
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -461,66 +461,10 @@ public static class KernelExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unidentified kernel NIDs observed at runtime; stubbed to return 0 until they are
|
|
||||||
// resolved against scripts/ps5_names.txt.
|
|
||||||
[SysAbiExport(
|
|
||||||
Nid = "9T2pDF2Ryqg",
|
|
||||||
ExportName = "sceKernelUnknown9T2p",
|
|
||||||
Target = Generation.Gen4 | Generation.Gen5,
|
|
||||||
LibraryName = "libKernel")]
|
|
||||||
public static int KernelUnknown9T2p(CpuContext ctx) => 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
|
// NOTE: "SHtAad20YYM"/"DIxvoy7Ngvk" are sce::Json::Value::getType/getInteger, and
|
||||||
// "3GPpjQdAMTw"/"9rAeANT2tyE"/"tsvEmnenz48" are __cxa_guard_acquire/__cxa_guard_release/
|
// "3GPpjQdAMTw"/"9rAeANT2tyE"/"tsvEmnenz48" are __cxa_guard_acquire/__cxa_guard_release/
|
||||||
// __cxa_atexit (verified by hashing against scripts/ps5_names.txt). Do not register them
|
// __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
|
// 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.
|
// shadowing them breaks every C++ static-init guard in the game.
|
||||||
|
|
||||||
private static int SetZeroReturn(CpuContext ctx)
|
|
||||||
{
|
|
||||||
ctx[CpuRegister.Rax] = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,15 @@ public static class KernelMemoryCompatExports
|
|||||||
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
|
private static readonly HashSet<string> _negativeStatCache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private static readonly ConcurrentDictionary<string, ulong> _aprFileSizeCache = new(StringComparer.OrdinalIgnoreCase);
|
private static readonly ConcurrentDictionary<string, ulong> _aprFileSizeCache = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private static long _nextFileDescriptor = 2;
|
private static long _nextFileDescriptor = 2;
|
||||||
|
|
||||||
|
internal static int AllocateGuestFileDescriptor()
|
||||||
|
{
|
||||||
|
lock (_fdGate)
|
||||||
|
{
|
||||||
|
return (int)Interlocked.Increment(ref _nextFileDescriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static ulong _nextPhysicalAddress;
|
private static ulong _nextPhysicalAddress;
|
||||||
private static ulong _nextVirtualAddress;
|
private static ulong _nextVirtualAddress;
|
||||||
private static ulong _mainDirectMemoryPoolBase = UnsetMainDirectMemoryPoolBase;
|
private static ulong _mainDirectMemoryPoolBase = UnsetMainDirectMemoryPoolBase;
|
||||||
@@ -1555,9 +1564,10 @@ public static class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
var directoryFd = (int)Interlocked.Increment(ref _nextFileDescriptor);
|
int directoryFd;
|
||||||
lock (_fdGate)
|
lock (_fdGate)
|
||||||
{
|
{
|
||||||
|
directoryFd = AllocateGuestFileDescriptor();
|
||||||
_openDirectories[directoryFd] = new OpenDirectory
|
_openDirectories[directoryFd] = new OpenDirectory
|
||||||
{
|
{
|
||||||
Path = hostPath,
|
Path = hostPath,
|
||||||
@@ -1578,9 +1588,10 @@ public static class KernelMemoryCompatExports
|
|||||||
stream.Seek(0, SeekOrigin.End);
|
stream.Seek(0, SeekOrigin.End);
|
||||||
}
|
}
|
||||||
|
|
||||||
var fd = (int)Interlocked.Increment(ref _nextFileDescriptor);
|
int fd;
|
||||||
lock (_fdGate)
|
lock (_fdGate)
|
||||||
{
|
{
|
||||||
|
fd = AllocateGuestFileDescriptor();
|
||||||
_openFiles[fd] = stream;
|
_openFiles[fd] = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1953,6 +1964,12 @@ public static class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (KernelSocketCompatExports.TryCloseSocketFd(fd))
|
||||||
|
{
|
||||||
|
ctx[CpuRegister.Rax] = 0;
|
||||||
|
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
FileStream? stream;
|
FileStream? stream;
|
||||||
lock (_fdGate)
|
lock (_fdGate)
|
||||||
{
|
{
|
||||||
@@ -1996,6 +2013,17 @@ public static class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
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;
|
FileStream? stream;
|
||||||
lock (_fdGate)
|
lock (_fdGate)
|
||||||
{
|
{
|
||||||
@@ -2225,6 +2253,17 @@ public static class KernelMemoryCompatExports
|
|||||||
return (int)OrbisGen2Result.ORBIS_GEN2_OK;
|
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;
|
FileStream? stream;
|
||||||
lock (_fdGate)
|
lock (_fdGate)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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<int, EmulatedSocketState> 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<byte>(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<byte> 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<byte> 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<byte> 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<byte> 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<byte>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,6 +95,54 @@ public static class NetExports
|
|||||||
return ctx.SetReturn(0);
|
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(
|
[SysAbiExport(
|
||||||
Nid = "C4UgDHHPvdw",
|
Nid = "C4UgDHHPvdw",
|
||||||
ExportName = "sceNetResolverCreate",
|
ExportName = "sceNetResolverCreate",
|
||||||
|
|||||||
Reference in New Issue
Block a user