From 5afa5e1ffa231d73b96ed0133db505412ff24e9e Mon Sep 17 00:00:00 2001 From: ParantezTech Date: Sun, 21 Jun 2026 23:18:55 +0300 Subject: [PATCH] [hle] Add missing service exports --- .../CommonDialog/CommonDialogExports.cs | 38 +++++ .../GameUpdate/GameUpdateExports.cs | 24 +++ src/SharpEmu.Libs/Json/JsonExports.cs | 91 ++++++++++++ src/SharpEmu.Libs/Network/Http2Exports.cs | 79 ++++++++++ src/SharpEmu.Libs/Network/NetCtlExports.cs | 20 +++ src/SharpEmu.Libs/Network/NetExports.cs | 140 ++++++++++++++++++ src/SharpEmu.Libs/Network/SslExports.cs | 87 +++++++++++ src/SharpEmu.Libs/Np/NpWebApi2Exports.cs | 65 ++++++++ .../NpGameIntent/NpGameIntentExports.cs | 24 +++ .../SystemService/SystemServiceExports.cs | 23 +++ 10 files changed, 591 insertions(+) create mode 100644 src/SharpEmu.Libs/CommonDialog/CommonDialogExports.cs create mode 100644 src/SharpEmu.Libs/GameUpdate/GameUpdateExports.cs create mode 100644 src/SharpEmu.Libs/Json/JsonExports.cs create mode 100644 src/SharpEmu.Libs/Network/Http2Exports.cs create mode 100644 src/SharpEmu.Libs/Network/NetCtlExports.cs create mode 100644 src/SharpEmu.Libs/Network/NetExports.cs create mode 100644 src/SharpEmu.Libs/Network/SslExports.cs create mode 100644 src/SharpEmu.Libs/Np/NpWebApi2Exports.cs create mode 100644 src/SharpEmu.Libs/NpGameIntent/NpGameIntentExports.cs diff --git a/src/SharpEmu.Libs/CommonDialog/CommonDialogExports.cs b/src/SharpEmu.Libs/CommonDialog/CommonDialogExports.cs new file mode 100644 index 0000000..ba1e46d --- /dev/null +++ b/src/SharpEmu.Libs/CommonDialog/CommonDialogExports.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Threading; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.CommonDialog; + +public static class CommonDialogExports +{ + private const int AlreadySystemInitialized = unchecked((int)0x80B80002); + private static int _initialized; + + [SysAbiExport( + Nid = "uoUpLGNkygk", + ExportName = "sceCommonDialogInitialize", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceCommonDialog")] + public static int CommonDialogInitialize(CpuContext ctx) + { + var result = Interlocked.Exchange(ref _initialized, 1) == 0 + ? 0 + : AlreadySystemInitialized; + ctx[CpuRegister.Rax] = unchecked((ulong)result); + return result; + } + + [SysAbiExport( + Nid = "BQ3tey0JmQM", + ExportName = "sceCommonDialogIsUsed", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceCommonDialog")] + public static int CommonDialogIsUsed(CpuContext ctx) + { + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } +} diff --git a/src/SharpEmu.Libs/GameUpdate/GameUpdateExports.cs b/src/SharpEmu.Libs/GameUpdate/GameUpdateExports.cs new file mode 100644 index 0000000..745ca8f --- /dev/null +++ b/src/SharpEmu.Libs/GameUpdate/GameUpdateExports.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Threading; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.GameUpdate; + +public static class GameUpdateExports +{ + private static int _initialized; + + [SysAbiExport( + Nid = "YJtKLttI9fM", + ExportName = "sceGameUpdateInitialize", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceGameUpdate")] + public static int GameUpdateInitialize(CpuContext ctx) + { + Interlocked.Exchange(ref _initialized, 1); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } +} diff --git a/src/SharpEmu.Libs/Json/JsonExports.cs b/src/SharpEmu.Libs/Json/JsonExports.cs new file mode 100644 index 0000000..4d2522d --- /dev/null +++ b/src/SharpEmu.Libs/Json/JsonExports.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Json; + +public static class JsonExports +{ + [SysAbiExport( + Nid = "-hJRce8wn1U", + ExportName = "_ZN3sce4Json12MemAllocatorC2Ev", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceJson")] + public static int MemAllocatorConstructor(CpuContext ctx) + { + var thisAddress = ctx[CpuRegister.Rdi]; + TraceJson("MemAllocator.ctor", thisAddress, 0); + ctx[CpuRegister.Rax] = thisAddress; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "OcAgPxcq5Vk", + ExportName = "_ZN3sce4Json12MemAllocatorD2Ev", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceJson")] + public static int MemAllocatorDestructor(CpuContext ctx) + { + TraceJson("MemAllocator.dtor", ctx[CpuRegister.Rdi], 0); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "cK6bYHf-Q5E", + ExportName = "_ZN3sce4Json11InitializerC1Ev", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceJson")] + public static int InitializerConstructor(CpuContext ctx) + { + var thisAddress = ctx[CpuRegister.Rdi]; + TraceJson("Initializer.ctor", thisAddress, 0); + ctx[CpuRegister.Rax] = thisAddress; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "RujUxbr3haM", + ExportName = "_ZN3sce4Json11InitializerD1Ev", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceJson")] + public static int InitializerDestructor(CpuContext ctx) + { + TraceJson("Initializer.dtor", ctx[CpuRegister.Rdi], 0); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "Cxwy7wHq4J0", + ExportName = "_ZN3sce4Json11Initializer10initializeEPKNS0_13InitParameterE", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceJson")] + public static int InitializerInitialize(CpuContext ctx) + { + var thisAddress = ctx[CpuRegister.Rdi]; + var initParameterAddress = ctx[CpuRegister.Rsi]; + if (thisAddress == 0) + { + ctx[CpuRegister.Rax] = unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + TraceJson("Initializer.initialize", thisAddress, initParameterAddress); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + private static void TraceJson(string operation, ulong thisAddress, ulong argument) + { + if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_JSON"), "1", StringComparison.Ordinal)) + { + return; + } + + Console.Error.WriteLine( + $"[LOADER][TRACE] json.{operation} this=0x{thisAddress:X16} arg=0x{argument:X16}"); + } +} diff --git a/src/SharpEmu.Libs/Network/Http2Exports.cs b/src/SharpEmu.Libs/Network/Http2Exports.cs new file mode 100644 index 0000000..7f7085e --- /dev/null +++ b/src/SharpEmu.Libs/Network/Http2Exports.cs @@ -0,0 +1,79 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System; +using System.Collections.Concurrent; +using System.Threading; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Network; + +public static class Http2Exports +{ + private const int Http2ErrorInvalidId = unchecked((int)0x80436004); + private const int Http2ErrorInvalidArgument = unchecked((int)0x80436016); + + private static readonly ConcurrentDictionary _contexts = new(); + private static int _nextContextId; + + private sealed record Http2Context(int NetId, int SslId, ulong PoolSize, int MaxRequests); + + [SysAbiExport( + Nid = "3JCe3lCbQ8A", + ExportName = "sceHttp2Init", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceHttp2")] + public static int Http2Init(CpuContext ctx) + { + var netId = unchecked((int)ctx[CpuRegister.Rdi]); + var sslId = unchecked((int)ctx[CpuRegister.Rsi]); + var poolSize = ctx[CpuRegister.Rdx]; + var maxRequests = unchecked((int)ctx[CpuRegister.Rcx]); + + if (poolSize == 0 || maxRequests <= 0) + { + return SetReturn(ctx, Http2ErrorInvalidArgument); + } + + var id = Interlocked.Increment(ref _nextContextId); + _contexts[id] = new Http2Context(netId, sslId, poolSize, maxRequests); + + TraceHttp2("init", id, unchecked((ulong)netId), unchecked((ulong)sslId), poolSize, unchecked((ulong)maxRequests)); + ctx[CpuRegister.Rax] = unchecked((ulong)id); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "YiBUtz-pGkc", + ExportName = "sceHttp2Term", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceHttp2")] + public static int Http2Term(CpuContext ctx) + { + var id = unchecked((int)ctx[CpuRegister.Rdi]); + if (!_contexts.TryRemove(id, out _)) + { + return SetReturn(ctx, Http2ErrorInvalidId); + } + + TraceHttp2("term", id, 0, 0, 0, 0); + return SetReturn(ctx, 0); + } + + private static int SetReturn(CpuContext ctx, int result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)result); + return result; + } + + private static void TraceHttp2(string operation, int id, ulong arg0, ulong arg1, ulong arg2, ulong arg3) + { + if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_HTTP2"), "1", StringComparison.Ordinal)) + { + return; + } + + Console.Error.WriteLine( + $"[LOADER][TRACE] http2.{operation} id={id} arg0=0x{arg0:X16} arg1=0x{arg1:X16} arg2=0x{arg2:X16} arg3=0x{arg3:X16}"); + } +} diff --git a/src/SharpEmu.Libs/Network/NetCtlExports.cs b/src/SharpEmu.Libs/Network/NetCtlExports.cs new file mode 100644 index 0000000..1c4eb27 --- /dev/null +++ b/src/SharpEmu.Libs/Network/NetCtlExports.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Network; + +public static class NetCtlExports +{ + [SysAbiExport( + Nid = "gky0+oaNM4k", + ExportName = "sceNetCtlInit", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNetCtl")] + public static int NetCtlInit(CpuContext ctx) + { + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } +} diff --git a/src/SharpEmu.Libs/Network/NetExports.cs b/src/SharpEmu.Libs/Network/NetExports.cs new file mode 100644 index 0000000..8852220 --- /dev/null +++ b/src/SharpEmu.Libs/Network/NetExports.cs @@ -0,0 +1,140 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System; +using System.Collections.Concurrent; +using System.Text; +using System.Threading; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Network; + +public static class NetExports +{ + private const int NetErrorBadFileDescriptor = unchecked((int)0x80410109); + private const int NetErrorInvalidArgument = unchecked((int)0x80410116); + private const int MaxNameLength = 256; + + private static readonly ConcurrentDictionary _pools = new(); + private static int _nextPoolId; + private static bool _initialized; + + private sealed record NetPool(string Name, int Size, int Flags); + + [SysAbiExport( + Nid = "Nlev7Lg8k3A", + ExportName = "sceNetInit", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetInit(CpuContext ctx) + { + _initialized = true; + TraceNet("init", 0, 0, 0, 0); + return SetReturn(ctx, 0); + } + + [SysAbiExport( + Nid = "cTGkc6-TBlI", + ExportName = "sceNetTerm", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetTerm(CpuContext ctx) + { + _initialized = false; + _pools.Clear(); + TraceNet("term", 0, 0, 0, 0); + return SetReturn(ctx, 0); + } + + [SysAbiExport( + Nid = "dgJBaeJnGpo", + ExportName = "sceNetPoolCreate", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetPoolCreate(CpuContext ctx) + { + var nameAddress = ctx[CpuRegister.Rdi]; + var size = unchecked((int)ctx[CpuRegister.Rsi]); + var flags = unchecked((int)ctx[CpuRegister.Rdx]); + + if (size <= 0) + { + return SetReturn(ctx, NetErrorInvalidArgument); + } + + var name = TryReadUtf8Z(ctx, nameAddress, MaxNameLength, out var value) + ? value + : string.Empty; + + var id = Interlocked.Increment(ref _nextPoolId); + _pools[id] = new NetPool(name, size, flags); + + TraceNet("pool.create", id, unchecked((ulong)size), unchecked((ulong)flags), _initialized ? 1UL : 0UL); + ctx[CpuRegister.Rax] = unchecked((ulong)id); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "K7RlrTkI-mw", + ExportName = "sceNetPoolDestroy", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNet")] + public static int NetPoolDestroy(CpuContext ctx) + { + var id = unchecked((int)ctx[CpuRegister.Rdi]); + if (!_pools.TryRemove(id, out _)) + { + return SetReturn(ctx, NetErrorBadFileDescriptor); + } + + TraceNet("pool.destroy", id, 0, 0, _initialized ? 1UL : 0UL); + return SetReturn(ctx, 0); + } + + private static int SetReturn(CpuContext ctx, int result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)result); + return result; + } + + private static bool TryReadUtf8Z(CpuContext ctx, ulong address, int maxLength, out string value) + { + value = string.Empty; + if (address == 0) + { + return true; + } + + Span one = stackalloc byte[1]; + var bytes = new byte[maxLength]; + var count = 0; + for (; count < maxLength; count++) + { + if (!ctx.Memory.TryRead(address + (ulong)count, one)) + { + return false; + } + + if (one[0] == 0) + { + break; + } + + bytes[count] = one[0]; + } + + value = Encoding.UTF8.GetString(bytes, 0, count); + return true; + } + + private static void TraceNet(string operation, int id, ulong arg0, ulong arg1, ulong arg2) + { + if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NET"), "1", StringComparison.Ordinal)) + { + return; + } + + Console.Error.WriteLine( + $"[LOADER][TRACE] net.{operation} id={id} arg0=0x{arg0:X16} arg1=0x{arg1:X16} arg2=0x{arg2:X16}"); + } +} diff --git a/src/SharpEmu.Libs/Network/SslExports.cs b/src/SharpEmu.Libs/Network/SslExports.cs new file mode 100644 index 0000000..4eeb632 --- /dev/null +++ b/src/SharpEmu.Libs/Network/SslExports.cs @@ -0,0 +1,87 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System; +using System.Collections.Concurrent; +using System.Threading; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Network; + +public static class SslExports +{ + private const int SslErrorInvalidId = unchecked((int)0x8095F006); + private const int SslErrorOutOfSize = unchecked((int)0x8095F008); + + private static readonly ConcurrentDictionary _contexts = new(); + private static int _nextContextId; + + private sealed record SslContext(ulong PoolSize); + + [SysAbiExport( + Nid = "hdpVEUDFW3s", + ExportName = "sceSslInit", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceSsl")] + public static int SslInit(CpuContext ctx) + { + var poolSize = ctx[CpuRegister.Rdi]; + if (poolSize == 0) + { + return SetReturn(ctx, SslErrorOutOfSize); + } + + var id = Interlocked.Increment(ref _nextContextId); + _contexts[id] = new SslContext(poolSize); + + TraceSsl("init", id, poolSize); + ctx[CpuRegister.Rax] = unchecked((ulong)id); + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } + + [SysAbiExport( + Nid = "0K1yQ6Lv-Yc", + ExportName = "sceSslTerm", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceSsl")] + public static int SslTerm(CpuContext ctx) + { + var id = unchecked((int)ctx[CpuRegister.Rdi]); + if (!_contexts.TryRemove(id, out _)) + { + return SetReturn(ctx, SslErrorInvalidId); + } + + TraceSsl("term", id, 0); + return SetReturn(ctx, 0); + } + + [SysAbiExport( + Nid = "viRXSHZYd0c", + ExportName = "sceSslClose", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceSsl")] + public static int SslClose(CpuContext ctx) + { + var id = unchecked((int)ctx[CpuRegister.Rdi]); + TraceSsl("close", id, 0); + return SetReturn(ctx, 0); + } + + private static int SetReturn(CpuContext ctx, int result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)result); + return result; + } + + private static void TraceSsl(string operation, int id, ulong arg0) + { + if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_SSL"), "1", StringComparison.Ordinal)) + { + return; + } + + Console.Error.WriteLine( + $"[LOADER][TRACE] ssl.{operation} id={id} arg0=0x{arg0:X16}"); + } +} diff --git a/src/SharpEmu.Libs/Np/NpWebApi2Exports.cs b/src/SharpEmu.Libs/Np/NpWebApi2Exports.cs new file mode 100644 index 0000000..28b7afa --- /dev/null +++ b/src/SharpEmu.Libs/Np/NpWebApi2Exports.cs @@ -0,0 +1,65 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System; +using System.Threading; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Np; + +public static class NpWebApi2Exports +{ + private const int NpWebApi2ErrorInvalidArgument = unchecked((int)0x80553402); + + private static int _initialized; + + [SysAbiExport( + Nid = "+o9816YQhqQ", + ExportName = "sceNpWebApi2Initialize", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNpWebApi2")] + public static int NpWebApi2Initialize(CpuContext ctx) + { + var httpContextId = unchecked((int)ctx[CpuRegister.Rdi]); + var poolSize = ctx[CpuRegister.Rsi]; + + if (httpContextId <= 0 || poolSize == 0) + { + return SetReturn(ctx, NpWebApi2ErrorInvalidArgument); + } + + Interlocked.Exchange(ref _initialized, 1); + TraceNpWebApi2("init", httpContextId, poolSize); + return SetReturn(ctx, 0); + } + + [SysAbiExport( + Nid = "bEvXpcEk200", + ExportName = "sceNpWebApi2Terminate", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNpWebApi2")] + public static int NpWebApi2Terminate(CpuContext ctx) + { + var libraryContextId = unchecked((int)ctx[CpuRegister.Rdi]); + Interlocked.Exchange(ref _initialized, 0); + TraceNpWebApi2("term", libraryContextId, 0); + return SetReturn(ctx, 0); + } + + private static int SetReturn(CpuContext ctx, int result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)result); + return result; + } + + private static void TraceNpWebApi2(string operation, int id, ulong arg0) + { + if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NP_WEB_API2"), "1", StringComparison.Ordinal)) + { + return; + } + + Console.Error.WriteLine( + $"[LOADER][TRACE] npwebapi2.{operation} id={id} arg0=0x{arg0:X16} initialized={Volatile.Read(ref _initialized)}"); + } +} diff --git a/src/SharpEmu.Libs/NpGameIntent/NpGameIntentExports.cs b/src/SharpEmu.Libs/NpGameIntent/NpGameIntentExports.cs new file mode 100644 index 0000000..06e2410 --- /dev/null +++ b/src/SharpEmu.Libs/NpGameIntent/NpGameIntentExports.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Threading; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.NpGameIntent; + +public static class NpGameIntentExports +{ + private static int _initialized; + + [SysAbiExport( + Nid = "m87BHxt-H60", + ExportName = "sceNpGameIntentInitialize", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceNpGameIntent")] + public static int NpGameIntentInitialize(CpuContext ctx) + { + Interlocked.Exchange(ref _initialized, 1); + ctx[CpuRegister.Rax] = 0; + return (int)OrbisGen2Result.ORBIS_GEN2_OK; + } +} diff --git a/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs b/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs index cb63ffc..93e704b 100644 --- a/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs +++ b/src/SharpEmu.Libs/SystemService/SystemServiceExports.cs @@ -10,6 +10,7 @@ public static class SystemServiceExports { private const int OrbisSystemServiceErrorParameter = unchecked((int)0x80A10003); private const int SystemServiceStatusSize = 0x0C; + private const int DisplaySafeAreaInfoSize = sizeof(float) + 128; [SysAbiExport( Nid = "rPo6tV8D9bM", @@ -34,6 +35,28 @@ public static class SystemServiceExports : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } + [SysAbiExport( + Nid = "1n37q1Bvc5Y", + ExportName = "sceSystemServiceGetDisplaySafeAreaInfo", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceSystemService")] + public static int SystemServiceGetDisplaySafeAreaInfo(CpuContext ctx) + { + var infoAddress = ctx[CpuRegister.Rdi]; + if (infoAddress == 0) + { + return SetReturn(ctx, OrbisSystemServiceErrorParameter); + } + + Span info = stackalloc byte[DisplaySafeAreaInfoSize]; + info.Clear(); + BinaryPrimitives.WriteSingleLittleEndian(info, 1.0f); + + return ctx.Memory.TryWrite(infoAddress, info) + ? SetReturn(ctx, 0) + : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + private static int SetReturn(CpuContext ctx, int result) { ctx[CpuRegister.Rax] = unchecked((ulong)result);