diff --git a/src/SharpEmu.Libs/Np/NpManagerExports.cs b/src/SharpEmu.Libs/Np/NpManagerExports.cs new file mode 100644 index 0000000..e0d50f5 --- /dev/null +++ b/src/SharpEmu.Libs/Np/NpManagerExports.cs @@ -0,0 +1,67 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Np; + +public static class NpManagerExports +{ + private const int NpTitleIdSize = 16; + private const int NpTitleSecretSize = 128; + + [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 SetReturn(ctx, 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 SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } + + TraceNp($"set_np_title_id title='{ReadTitleId(titleId)}'"); + return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); + } + + private static int SetReturn(CpuContext ctx, OrbisGen2Result result) + { + ctx[CpuRegister.Rax] = unchecked((ulong)(int)result); + return (int)result; + } + + 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}"); + } +}