From 4c8c67a3dd4fccba03700f55c30b60b94d466191 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:17:40 -0400 Subject: [PATCH] fix: Add ASTRO BOT compatibility stubs (#481) * Add ASTRO BOT compatibility stubs * Fix ASTRO BOT compatibility stubs --- .../DirectExecutionBackend.Exceptions.cs | 2 +- .../Cpu/Native/DirectExecutionBackend.cs | 4 +- src/SharpEmu.Libs/Acm/AcmExports.cs | 44 ++++++++++ src/SharpEmu.Libs/Json/JsonExports.cs | 11 +++ .../Kernel/KernelMemoryCompatExports.cs | 20 +++++ src/SharpEmu.Libs/Np/NpWebApi2Exports.cs | 88 +++++++++++++++++-- src/SharpEmu.Libs/SaveData/SaveDataExports.cs | 82 ++++++++++++++++- src/SharpEmu.Libs/Share/ShareExports.cs | 53 +++++++++++ .../UserService/UserServiceExports.cs | 12 ++- .../VideoOut/VulkanVideoPresenter.cs | 34 ++++--- 10 files changed, 322 insertions(+), 28 deletions(-) create mode 100644 src/SharpEmu.Libs/Acm/AcmExports.cs diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs index f5da11c7..4f7aba84 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs @@ -483,7 +483,7 @@ public sealed partial class DirectExecutionBackend if (count <= 16 || count % 65536 == 0) { Console.Error.WriteLine( - $"[LOADER][WARN] Ignored guest int 0x41 trap #{count} at 0x{rip:X16} (SHARPEMU_IGNORE_INT41=1)"); + $"[LOADER][WARN] Ignored guest int 0x41 trap #{count} at 0x{rip:X16} (default-on; set SHARPEMU_IGNORE_INT41=0 to disable)"); Console.Error.Flush(); } return true; diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index d5ba75d9..b1a817b4 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -1123,7 +1123,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _logStrlenBursts = _logStrlenImports || string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_STRLEN_BURSTS"), "1", StringComparison.Ordinal); _logGuestContext = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_CONTEXT"), "1", StringComparison.Ordinal); - _ignoreGuestInt41 = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_IGNORE_INT41"), "1", StringComparison.Ordinal); + var ignoreGuestInt41Env = Environment.GetEnvironmentVariable("SHARPEMU_IGNORE_INT41"); + _ignoreGuestInt41 = !string.Equals(ignoreGuestInt41Env, "0", StringComparison.Ordinal) && + !string.Equals(ignoreGuestInt41Env, "false", StringComparison.OrdinalIgnoreCase); _ignoredGuestInt41Count = 0; _logGuestThreads = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUEST_THREADS"), "1", StringComparison.Ordinal); _logUsleep = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal); diff --git a/src/SharpEmu.Libs/Acm/AcmExports.cs b/src/SharpEmu.Libs/Acm/AcmExports.cs new file mode 100644 index 00000000..36da90d7 --- /dev/null +++ b/src/SharpEmu.Libs/Acm/AcmExports.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Buffers.Binary; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Acm; + +public static class AcmExports +{ + private static int _nextContextHandle; + + [SysAbiExport( + Nid = "ZIXln2K3XMk", + ExportName = "sceAcmContextCreate", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceAcm")] + public static int AcmContextCreate(CpuContext ctx) + { + var outContextAddress = ctx[CpuRegister.Rdi]; + if (outContextAddress == 0) + { + return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); + } + + var handle = (ulong)Interlocked.Increment(ref _nextContextHandle); + Span handleBytes = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian(handleBytes, handle); + return ctx.Memory.TryWrite(outContextAddress, handleBytes) + ? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK) + : ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + [SysAbiExport( + Nid = "jBgBjAj02R8", + ExportName = "sceAcmContextDestroy", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceAcm")] + public static int AcmContextDestroy(CpuContext ctx) + { + _ = ctx; + return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK); + } +} diff --git a/src/SharpEmu.Libs/Json/JsonExports.cs b/src/SharpEmu.Libs/Json/JsonExports.cs index 322351e7..6699228e 100644 --- a/src/SharpEmu.Libs/Json/JsonExports.cs +++ b/src/SharpEmu.Libs/Json/JsonExports.cs @@ -119,6 +119,17 @@ public static class JsonExports return SetReturn(ctx, 0); } + // Catalog alias NID for the same callback setter. + #pragma warning disable SHEM004 + [SysAbiExport( + Nid = "00oCq0RwSAY", + ExportName = "_ZN3sce4Json11Initializer27setGlobalNullAccessCallbackEPFRKNS0_5ValueENS0_9ValueTypeEPS3_PvES7_", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceJson")] + public static int InitializerSetGlobalNullAccessCallbackAlt(CpuContext ctx) => + InitializerSetGlobalNullAccessCallback(ctx); + #pragma warning restore SHEM004 + [SysAbiExport( Nid = "WSOuge5IsCg", ExportName = "_ZN3sce4Json14InitParameter2C1Ev", diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 8be2b007..e948d43a 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -2661,6 +2661,26 @@ public static partial class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_OK; } + [SysAbiExport( + Nid = "n1-v6FgU7MQ", + ExportName = "sceKernelConfiguredFlexibleMemorySize", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libKernel")] + public static int KernelConfiguredFlexibleMemorySize(CpuContext ctx) + { + var outSizeAddress = ctx[CpuRegister.Rdi]; + if (outSizeAddress == 0) + { + return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; + } + + Span sizeBytes = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian(sizeBytes, FlexibleMemorySizeBytes); + return ctx.Memory.TryWrite(outSizeAddress, sizeBytes) + ? (int)OrbisGen2Result.ORBIS_GEN2_OK + : (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; + } + [SysAbiExport( Nid = "rTXw65xmLIA", ExportName = "sceKernelAllocateDirectMemory", diff --git a/src/SharpEmu.Libs/Np/NpWebApi2Exports.cs b/src/SharpEmu.Libs/Np/NpWebApi2Exports.cs index d039d609..20253e51 100644 --- a/src/SharpEmu.Libs/Np/NpWebApi2Exports.cs +++ b/src/SharpEmu.Libs/Np/NpWebApi2Exports.cs @@ -10,6 +10,11 @@ public static class NpWebApi2Exports private const int NpWebApi2ErrorInvalidArgument = unchecked((int)0x80553402); private static int _initialized; + private static int _nextLibraryContextHandle; + private static int _nextPushEventHandle; + private static int _nextUserContextHandle = 1000; + private static readonly object _contextGate = new(); + private static readonly HashSet _libraryContexts = []; [SysAbiExport( Nid = "+o9816YQhqQ", @@ -26,9 +31,10 @@ public static class NpWebApi2Exports return ctx.SetReturn(NpWebApi2ErrorInvalidArgument); } + var libraryContextId = CreateLibraryContextId(); Interlocked.Exchange(ref _initialized, 1); TraceNpWebApi2("init", httpContextId, poolSize); - return ctx.SetReturn(0); + return ctx.SetReturn(libraryContextId); } [SysAbiExport( @@ -38,9 +44,16 @@ public static class NpWebApi2Exports LibraryName = "libSceNpWebApi2")] public static int NpWebApi2InitializeAlt(CpuContext ctx) { + var libraryContextId = unchecked((int)ctx[CpuRegister.Rdi]); + if (!IsValidLibraryContextId(libraryContextId)) + { + return ctx.SetReturn(NpWebApi2ErrorInvalidArgument); + } + + var handle = CreatePushEventHandle(); Interlocked.Exchange(ref _initialized, 1); - TraceNpWebApi2("init-alt", unchecked((int)ctx[CpuRegister.Rdi]), ctx[CpuRegister.Rsi]); - return ctx.SetReturn(0); + TraceNpWebApi2("init-alt", libraryContextId, 0); + return ctx.SetReturn(handle); } [SysAbiExport( @@ -50,10 +63,23 @@ public static class NpWebApi2Exports LibraryName = "libSceNpWebApi2")] public static int NpWebApi2CreateUserContext(CpuContext ctx) { - // No PSN backend: refuse user-context creation so the title's online - // layer backs off instead of driving a half-created context handle. - TraceNpWebApi2("create-user-context", unchecked((int)ctx[CpuRegister.Rdi]), ctx[CpuRegister.Rsi]); - return ctx.SetReturn(NpWebApi2ErrorInvalidArgument); + var libraryContextId = unchecked((int)ctx[CpuRegister.Rdi]); + var userId = unchecked((int)ctx[CpuRegister.Rsi]); + + TraceNpWebApi2( + "create-user-context", + libraryContextId, + unchecked((uint)userId)); + + if (Volatile.Read(ref _initialized) == 0 || + !IsValidLibraryContextId(libraryContextId) || + userId == -1) + { + return ctx.SetReturn(NpWebApi2ErrorInvalidArgument); + } + + var userContextId = Interlocked.Increment(ref _nextUserContextHandle); + return ctx.SetReturn(userContextId); } [SysAbiExport( @@ -64,11 +90,57 @@ public static class NpWebApi2Exports public static int NpWebApi2Terminate(CpuContext ctx) { var libraryContextId = unchecked((int)ctx[CpuRegister.Rdi]); - Interlocked.Exchange(ref _initialized, 0); + if (!IsValidLibraryContextId(libraryContextId)) + { + return ctx.SetReturn(NpWebApi2ErrorInvalidArgument); + } + + RemoveLibraryContextId(libraryContextId); TraceNpWebApi2("term", libraryContextId, 0); return ctx.SetReturn(0); } + private static int CreateLibraryContextId() + { + var handle = Interlocked.Increment(ref _nextLibraryContextHandle); + lock (_contextGate) + { + _libraryContexts.Add(handle); + } + + return handle; + } + + private static int CreatePushEventHandle() + { + return Interlocked.Increment(ref _nextPushEventHandle); + } + + private static bool IsValidLibraryContextId(int libraryContextId) + { + if (libraryContextId <= 0 || libraryContextId >= 0x8000) + { + return false; + } + + lock (_contextGate) + { + return _libraryContexts.Contains(libraryContextId); + } + } + + private static void RemoveLibraryContextId(int libraryContextId) + { + lock (_contextGate) + { + _libraryContexts.Remove(libraryContextId); + if (_libraryContexts.Count == 0) + { + Interlocked.Exchange(ref _initialized, 0); + } + } + } + private static void TraceNpWebApi2(string operation, int id, ulong arg0) { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NP_WEB_API2"), "1", StringComparison.Ordinal)) diff --git a/src/SharpEmu.Libs/SaveData/SaveDataExports.cs b/src/SharpEmu.Libs/SaveData/SaveDataExports.cs index a3a9e16e..97b5ccbe 100644 --- a/src/SharpEmu.Libs/SaveData/SaveDataExports.cs +++ b/src/SharpEmu.Libs/SaveData/SaveDataExports.cs @@ -28,6 +28,7 @@ public static class SaveDataExports private const ulong ResultInfosOffset = 0x20; private const uint SortKeyFreeBlocks = 5; private const uint SortOrderDescent = 1; + private const uint MountModeReadOnly = 1u << 0; private const uint MountModeCreate = 1u << 2; private const uint MountModeCreate2 = 1u << 5; private const int MountResultSize = 0x40; @@ -713,16 +714,43 @@ public static class SaveDataExports return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } - if (userId < 0 || string.IsNullOrWhiteSpace(dirName)) + return MountSaveData( + ctx, + "mount3", + userId, + ResolveConfiguredTitleId(), + dirName, + blocks, + systemBlocks, + mountMode, + resource, + mode, + resultAddress); + } + + private static int MountSaveData( + CpuContext ctx, + string operation, + int userId, + string titleId, + string dirName, + ulong blocks, + ulong systemBlocks, + uint mountMode, + uint resource, + uint mode, + ulong resultAddress) + { + if (userId < 0 || string.IsNullOrWhiteSpace(titleId) || string.IsNullOrWhiteSpace(dirName)) { return SetReturn(ctx, OrbisSaveDataErrorParameter); } try { - var titleId = ResolveConfiguredTitleId(); + var sanitizedTitleId = SanitizePathSegment(titleId.Trim()); var savePath = Path.Combine( - ResolveTitleSaveRoot(userId, titleId), + ResolveTitleSaveRoot(userId, sanitizedTitleId), SanitizePathSegment(dirName)); var existed = Directory.Exists(savePath); var create = (mountMode & MountModeCreate) != 0; @@ -760,7 +788,7 @@ public static class SaveDataExports } TraceSaveData( - $"mount3 user={userId} title={titleId} dir={dirName} blocks={blocks} " + + $"{operation} user={userId} title={sanitizedTitleId} dir={dirName} blocks={blocks} " + $"system_blocks={systemBlocks} mount_mode=0x{mountMode:X} resource={resource} mode={mode} " + $"mount_point={mountPoint} created={!existed} root='{savePath}'"); return SetReturn(ctx, 0); @@ -779,6 +807,52 @@ public static class SaveDataExports } } + [SysAbiExport( + Nid = "WAzWTZm1H+I", + ExportName = "sceSaveDataTransferringMount", + Target = Generation.Gen5, + LibraryName = "libSceSaveData")] + public static int SaveDataTransferringMount(CpuContext ctx) + { + var mountAddress = ctx[CpuRegister.Rdi]; + var resultAddress = ctx[CpuRegister.Rsi]; + if (mountAddress == 0 || resultAddress == 0) + { + return SetReturn(ctx, OrbisSaveDataErrorParameter); + } + + if (!TryReadInt32(ctx, mountAddress, out var userId) || + !ctx.TryReadUInt64(mountAddress + 0x08, out var titleIdAddress) || + !ctx.TryReadUInt64(mountAddress + 0x10, out var dirNameAddress) || + titleIdAddress == 0 || + dirNameAddress == 0 || + !TryReadFixedAscii(ctx, titleIdAddress, SaveDataTitleIdSize, out var titleId) || + !TryReadFixedAscii(ctx, dirNameAddress, SaveDataDirNameSize, out var dirName)) + { + return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + return MountSaveData( + ctx, + "transferring_mount", + userId, + titleId, + dirName, + 0, + 0, + MountModeReadOnly, + 0, + 0, + resultAddress); + } + + [SysAbiExport( + Nid = "RjMlsR8EXrw", + ExportName = "sceSaveDataTransferringMountPs4", + Target = Generation.Gen5, + LibraryName = "libSceSaveData")] + public static int SaveDataTransferringMountPs4(CpuContext ctx) => SaveDataTransferringMount(ctx); + private static int _nextTransactionResource; [SysAbiExport( Nid = "gjRZNnw0JPE", diff --git a/src/SharpEmu.Libs/Share/ShareExports.cs b/src/SharpEmu.Libs/Share/ShareExports.cs index cc6a271d..e6022f6d 100644 --- a/src/SharpEmu.Libs/Share/ShareExports.cs +++ b/src/SharpEmu.Libs/Share/ShareExports.cs @@ -12,6 +12,9 @@ public static class ShareExports private static int _initialized; private static string _contentParam = string.Empty; + private static readonly object _callbackGate = new(); + private static ulong _contentEventCallback; + private static ulong _contentEventCallbackArgument; [SysAbiExport( Nid = "nBDD66kiFW8", @@ -62,6 +65,56 @@ public static class ShareExports return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK); } + [SysAbiExport( + Nid = "Sygnk9dr5WQ", + ExportName = "sceShareRegisterContentEventCallback", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceShareUtility")] + public static int ShareRegisterContentEventCallback(CpuContext ctx) + { + var callback = ctx[CpuRegister.Rdi]; + var argument = ctx[CpuRegister.Rsi]; + if (callback == 0) + { + return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); + } + + lock (_callbackGate) + { + _contentEventCallback = callback; + _contentEventCallbackArgument = argument; + } + + TraceShare($"register_content_event_callback fn=0x{callback:X16} arg=0x{argument:X16}"); + return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK); + } + + [SysAbiExport( + Nid = "KnsfHKmZqFA", + ExportName = "sceShareUnregisterContentEventCallback", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceShareUtility")] + public static int ShareUnregisterContentEventCallback(CpuContext ctx) + { + var callback = ctx[CpuRegister.Rdi]; + if (callback == 0) + { + return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); + } + + lock (_callbackGate) + { + if (_contentEventCallback == callback) + { + _contentEventCallback = 0; + _contentEventCallbackArgument = 0; + } + } + + TraceShare($"unregister_content_event_callback fn=0x{callback:X16}"); + return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK); + } + private static bool TryReadNullTerminatedUtf8(CpuContext ctx, ulong address, int maxLength, out string value) { Span bytes = stackalloc byte[maxLength]; diff --git a/src/SharpEmu.Libs/UserService/UserServiceExports.cs b/src/SharpEmu.Libs/UserService/UserServiceExports.cs index 2a4724d8..f395d51c 100644 --- a/src/SharpEmu.Libs/UserService/UserServiceExports.cs +++ b/src/SharpEmu.Libs/UserService/UserServiceExports.cs @@ -118,7 +118,7 @@ public static class UserServiceExports var userId = unchecked((int)ctx[CpuRegister.Rdi]); var nameAddress = ctx[CpuRegister.Rsi]; var capacity = ctx[CpuRegister.Rdx]; - if (userId != PrimaryUserId) + if (userId != PrimaryUserId && userId != 1) { return SetReturn(ctx, OrbisUserServiceErrorInvalidParameter); } @@ -144,6 +144,16 @@ public static class UserServiceExports : SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } + // Title-captured alias NID for the same username query. + #pragma warning disable SHEM004 + [SysAbiExport( + Nid = "znaWI0gpuo8", + ExportName = "sceUserServiceGetUserName", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceUserService")] + public static int UserServiceGetUserNameAlt(CpuContext ctx) => UserServiceGetUserName(ctx); + #pragma warning restore SHEM004 + // Name not yet in ps5_names.txt and the NID was captured from titles; revisit when the symbol is catalogued. #pragma warning disable SHEM006 [SysAbiExport( diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs index bb8c1c71..4ebcc487 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.cs @@ -3251,19 +3251,27 @@ internal static unsafe class VulkanVideoPresenter } } - WaitForRenderDocAttachIfRequested(); - _vk = Vk.GetApi(); - CreateInstance(); - CreateSurface(); - SelectPhysicalDevice(); - CreateDevice(); - CreatePipelineCache(); - CreateSwapchain(); - CreateCommandResources(); - CreateGuestDrawResources(); - _vulkanReady = true; - Console.Error.WriteLine( - $"[LOADER][INFO] Vulkan VideoOut ready: {_extent.Width}x{_extent.Height}, format={_swapchainFormat}"); + try + { + WaitForRenderDocAttachIfRequested(); + _vk = Vk.GetApi(); + CreateInstance(); + CreateSurface(); + SelectPhysicalDevice(); + CreateDevice(); + CreatePipelineCache(); + CreateSwapchain(); + CreateCommandResources(); + CreateGuestDrawResources(); + _vulkanReady = true; + Console.Error.WriteLine( + $"[LOADER][INFO] Vulkan VideoOut ready: {_extent.Width}x{_extent.Height}, format={_swapchainFormat}"); + } + catch (Exception exception) + { + _vulkanReady = false; + Console.Error.WriteLine($"[LOADER][WARN] Vulkan VideoOut disabled: {exception.Message}"); + } } private static void WaitForRenderDocAttachIfRequested()