fix: Add ASTRO BOT compatibility stubs (#481)

* Add ASTRO BOT compatibility stubs

* Fix ASTRO BOT compatibility stubs
This commit is contained in:
Kurt Himebauch
2026-07-21 11:17:40 -04:00
committed by GitHub
parent ada67a1924
commit 4c8c67a3dd
10 changed files with 322 additions and 28 deletions
@@ -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;
@@ -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);
+44
View File
@@ -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<byte> 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);
}
}
+11
View File
@@ -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",
@@ -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<byte> 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",
+80 -8
View File
@@ -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<int> _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))
+78 -4
View File
@@ -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",
+53
View File
@@ -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<byte> bytes = stackalloc byte[maxLength];
@@ -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(
@@ -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()