diff --git a/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs index 061afd8..8861fff 100644 --- a/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelEventQueueCompatExports.cs @@ -558,6 +558,9 @@ public static class KernelEventQueueCompatExports : (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } + // The user event id is an int that the kernel stores in a uintptr_t ident, so -1 becomes 0xFFFFFFFFFFFFFFFF. + private static ulong UserEventIdent(CpuContext ctx) => unchecked((ulong)(int)ctx[CpuRegister.Rsi]); + [SysAbiExport( Nid = "WDszmSbWuDk", ExportName = "sceKernelAddUserEventEdge", @@ -568,7 +571,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var registered = RegisterEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser, 0, KernelEventFlagClear); @@ -588,7 +591,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var registered = RegisterEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser, 0, flags: 0); @@ -608,7 +611,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var deleted = DeleteRegisteredEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser); TraceEventQueue(ctx, "delete_user", handle); return deleted @@ -626,7 +629,7 @@ public static class KernelEventQueueCompatExports var handle = ctx[CpuRegister.Rdi]; var triggered = TriggerRegisteredEvent( handle, - ctx[CpuRegister.Rsi], + UserEventIdent(ctx), KernelEventFilterUser, userData: ctx[CpuRegister.Rdx]); TraceEventQueue(ctx, "trigger_user", handle); diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs index 1ae18e6..80fae25 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.BackedMappings.cs @@ -494,6 +494,9 @@ public static partial class KernelMemoryCompatExports if (candidate > UserAddressLimit - length) { candidate = AlignUp(SparseReservationBase, alignment); + // A range too large for the aperture can only live at the guest's own hint. + if (candidate > UserAddressLimit - length) + candidate = AlignUp(requested, alignment); if (candidate == 0 || candidate > UserAddressLimit - length) return false; } diff --git a/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs b/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs index 823ba48..643c478 100644 --- a/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs +++ b/tests/SharpEmu.Libs.Tests/Kernel/KernelBackedMemoryTests.cs @@ -318,6 +318,25 @@ public sealed class KernelBackedMemoryTests Assert.Equal((address, address + 0x4000), test.Query(address)); } + [Fact] + public void ReservationTooLargeForTheSparseApertureKeepsTheGuestHint() + { + using var test = new BackedKernelMemory(); + const ulong hint = 0x1000000000; + const ulong size = 0xC000000000; + test.Host.FailReserveHoleWhen = (_, requestedSize) => requestedSize >= size; + Assert.True(test.Context.TryWriteUInt64(test.Output, hint)); + test.Context[CpuRegister.Rdi] = test.Output; + test.Context[CpuRegister.Rsi] = size; + test.Context[CpuRegister.Rdx] = 0; + test.Context[CpuRegister.Rcx] = 0x200000; + + Assert.Equal(0, KernelRuntimeCompatExports.KernelReserveVirtualRange(test.Context)); + Assert.True(test.Context.TryReadUInt64(test.Output, out var address)); + Assert.Equal(hint, address); + Assert.Equal((hint, hint + size), test.Query(hint)); + } + [Fact] public void AddressSearchUsesTheFullReservationExtent() { diff --git a/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs b/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs index 22de16e..33f7a94 100644 --- a/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs +++ b/tests/SharpEmu.Libs.Tests/Kernel/KernelEventQueueCompatExportsTests.cs @@ -110,24 +110,27 @@ public sealed class KernelEventQueueCompatExportsTests // the queued event with the registered ident/filter and the trigger data. // DequeueEvents runs before the blocking path, so a pre-triggered queue // returns immediately without touching the guest thread scheduler. - [Fact] - public void CreateAddTriggerWait_DeliversTriggeredUserEvent() + // The id is an int that the kernel stores in a uintptr_t ident, so -1 (0xFFFFFFFF in the register) + // is delivered as 0xFFFFFFFFFFFFFFFF. + [Theory] + [InlineData(0x4242UL, 0x4242UL)] + [InlineData(0xFFFF_FFFFUL, ulong.MaxValue)] + public void CreateAddTriggerWait_DeliversTriggeredUserEvent(ulong id, ulong eventIdent) { - const ulong eventIdent = 0x4242; const ulong triggerData = 0x55AA_55AA; var handle = CreateEqueue(); // Register the user event on the queue. var (addCtx, _) = NewContextWithOutSlot(); addCtx[CpuRegister.Rdi] = handle; - addCtx[CpuRegister.Rsi] = eventIdent; + addCtx[CpuRegister.Rsi] = id; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, KernelEventQueueCompatExports.KernelAddUserEvent(addCtx)); // Trigger it with a distinct data payload. var (triggerCtx, _) = NewContextWithOutSlot(); triggerCtx[CpuRegister.Rdi] = handle; - triggerCtx[CpuRegister.Rsi] = eventIdent; + triggerCtx[CpuRegister.Rsi] = id; triggerCtx[CpuRegister.Rdx] = triggerData; Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_OK, KernelEventQueueCompatExports.KernelTriggerUserEvent(triggerCtx));