// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; using System.Buffers.Binary; namespace SharpEmu.Libs.Np; public static class NpManagerExports { private const int NpTitleIdSize = 16; private const int NpTitleSecretSize = 128; private const int NpReachabilityStateUnavailable = 0; [SysAbiExport( Nid = "3Zl8BePTh9Y", ExportName = "sceNpCheckCallback", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpCheckCallback(CpuContext ctx) { ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } [SysAbiExport( Nid = "S7QTn72PrDw", ExportName = "sceNpDeleteRequest", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpDeleteRequest(CpuContext ctx) { ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } [SysAbiExport( Nid = "JELHf4xPufo", ExportName = "sceNpCheckCallbackForLib", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpCheckCallbackForLib(CpuContext ctx) { ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } // Offline profile: the online id payload is left untouched and the call // reports success, matching the other offline NpManager stubs here. [SysAbiExport( Nid = "XDncXQIJUSk", ExportName = "sceNpGetOnlineId", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpGetOnlineId(CpuContext ctx) { // Gen5 ABI: user ID, then output structure. return WriteOfflineOnlineId(ctx, ctx[CpuRegister.Rsi]); } [SysAbiExport( Nid = "rbknaUjpqWo", ExportName = "sceNpGetAccountIdA", Target = Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpGetOnlineIdA(CpuContext ctx) { // The A variant takes a user ID before OnlineId. return WriteOfflineOnlineId(ctx, ctx[CpuRegister.Rsi]); } [SysAbiExport( Nid = "e-ZuhGEoeC4", ExportName = "sceNpGetNpReachabilityState", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpGetNpReachabilityState(CpuContext ctx) { var stateAddress = ctx[CpuRegister.Rsi]; if (stateAddress == 0) { return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); } return ctx.TryWriteInt32(stateAddress, NpReachabilityStateUnavailable) ? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK) : ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } [SysAbiExport( Nid = "VfRSmPmj8Q8", ExportName = "sceNpRegisterStateCallback", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpRegisterStateCallback(CpuContext ctx) { ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } [SysAbiExport( Nid = "qQJfO8HAiaY", ExportName = "sceNpRegisterStateCallbackA", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpRegisterStateCallbackA(CpuContext ctx) { ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } [SysAbiExport( Nid = "0c7HbXRKUt4", ExportName = "sceNpRegisterStateCallbackForToolkit", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManagerForToolkit")] public static int NpRegisterStateCallbackForToolkit(CpuContext ctx) { ctx[CpuRegister.Rax] = 0; return (int)OrbisGen2Result.ORBIS_GEN2_OK; } [SysAbiExport( Nid = "eQH7nWPcAgc", ExportName = "sceNpGetState", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpGetState(CpuContext ctx) { var stateAddress = ctx[CpuRegister.Rsi]; if (stateAddress == 0) { return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); } Span stateBytes = stackalloc byte[sizeof(uint)]; BinaryPrimitives.WriteUInt32LittleEndian(stateBytes, 1); return ctx.Memory.TryWrite(stateAddress, stateBytes) ? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK) : ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } [SysAbiExport( Nid = "Ec63y59l9tw", ExportName = "sceNpSetNpTitleId", Target = Generation.Gen4 | Generation.Gen5, LibraryName = "libSceNpManager")] public static int NpSetNpTitleId(CpuContext ctx) { var titleIdAddress = ctx[CpuRegister.Rdi]; var titleSecretAddress = ctx[CpuRegister.Rsi]; if (titleIdAddress == 0 || titleSecretAddress == 0) { return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); } Span titleId = stackalloc byte[NpTitleIdSize]; Span titleSecret = stackalloc byte[NpTitleSecretSize]; if (!ctx.Memory.TryRead(titleIdAddress, titleId) || !ctx.Memory.TryRead(titleSecretAddress, titleSecret)) { return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } TraceNp($"set_np_title_id title='{ReadTitleId(titleId)}'"); return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK); } private static string ReadTitleId(ReadOnlySpan bytes) { var length = 0; while (length < 12 && length < bytes.Length && bytes[length] != 0) { length++; } return length == 0 ? string.Empty : System.Text.Encoding.ASCII.GetString(bytes[..length]); } private static void TraceNp(string message) { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_NP"), "1", StringComparison.Ordinal)) { return; } Console.Error.WriteLine($"[LOADER][TRACE] np.{message}"); } private static int WriteOfflineOnlineId(CpuContext ctx, ulong address) { if (address == 0) { return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT); } // SceNpOnlineId is a 16-byte handle plus four trailing bytes. Span onlineId = stackalloc byte[20]; "Player"u8.CopyTo(onlineId); return ctx.Memory.TryWrite(address, onlineId) ? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK) : ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); } }