diff --git a/src/SharpEmu.Libs/Random/RandomExports.cs b/src/SharpEmu.Libs/Random/RandomExports.cs new file mode 100644 index 00000000..e7876723 --- /dev/null +++ b/src/SharpEmu.Libs/Random/RandomExports.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Security.Cryptography; +using SharpEmu.HLE; + +namespace SharpEmu.Libs.Random; + +public static class RandomExports +{ + private const int RandomErrorInvalid = unchecked((int)0x817C0016); + private const int MaxRandomBytes = 64; + + [SysAbiExport( + Nid = "PI7jIZj4pcE", + ExportName = "sceRandomGetRandomNumber", + Target = Generation.Gen4 | Generation.Gen5, + LibraryName = "libSceRandom")] + public static int RandomGetRandomNumber(CpuContext ctx) + { + var destination = ctx[CpuRegister.Rdi]; + var size = ctx[CpuRegister.Rsi]; + if ((destination == 0 && size != 0) || size > MaxRandomBytes) + { + return ctx.SetReturn(RandomErrorInvalid); + } + + if (size == 0) + { + return ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK); + } + + Span bytes = stackalloc byte[(int)size]; + RandomNumberGenerator.Fill(bytes); + return ctx.Memory.TryWrite(destination, bytes) + ? ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_OK) + : ctx.SetReturn(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT); + } +} diff --git a/tests/SharpEmu.Libs.Tests/Random/RandomExportsTests.cs b/tests/SharpEmu.Libs.Tests/Random/RandomExportsTests.cs new file mode 100644 index 00000000..b046cbd1 --- /dev/null +++ b/tests/SharpEmu.Libs.Tests/Random/RandomExportsTests.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using SharpEmu.HLE; +using SharpEmu.Libs.Random; +using Xunit; + +namespace SharpEmu.Libs.Tests.Random; + +public sealed class RandomExportsTests +{ + private const ulong BaseAddress = 0x1000; + private const int RandomErrorInvalid = unchecked((int)0x817C0016); + + [Fact] + public void GetRandomNumberWritesRequestedBytes() + { + var memory = new FakeCpuMemory(BaseAddress, 64); + var ctx = CreateContext(memory, BaseAddress, 64); + + Assert.Equal(0, RandomExports.RandomGetRandomNumber(ctx)); + + var bytes = new byte[64]; + Assert.True(memory.TryRead(BaseAddress, bytes)); + Assert.NotEqual(new byte[64], bytes); + } + + [Theory] + [InlineData(0, 1)] + [InlineData(BaseAddress, 65)] + public void GetRandomNumberRejectsInvalidArguments(ulong address, ulong size) + { + var memory = new FakeCpuMemory(BaseAddress, 64); + var ctx = CreateContext(memory, address, size); + + Assert.Equal(RandomErrorInvalid, RandomExports.RandomGetRandomNumber(ctx)); + } + + [Fact] + public void GetRandomNumberAcceptsEmptyRequest() + { + var memory = new FakeCpuMemory(BaseAddress, 64); + var ctx = CreateContext(memory, 0, 0); + + Assert.Equal(0, RandomExports.RandomGetRandomNumber(ctx)); + } + + [Fact] + public void GetRandomNumberReportsUnmappedDestination() + { + var memory = new FakeCpuMemory(BaseAddress, 64); + var ctx = CreateContext(memory, BaseAddress + 64, 1); + + Assert.Equal( + (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT, + RandomExports.RandomGetRandomNumber(ctx)); + } + + private static CpuContext CreateContext( + ICpuMemory memory, + ulong destination, + ulong size) + { + var ctx = new CpuContext(memory, Generation.Gen5); + ctx[CpuRegister.Rdi] = destination; + ctx[CpuRegister.Rsi] = size; + return ctx; + } +}