Sdl backend (#670)

* [audio] added sdl audio backend and in-tree atrac9 decoder

* [input] replaced per-platform pad readers with sdl gamepad input

* [video] added sdl window and host display plumbing

* [gui] added host display options and per-game render settings

* [bink] synced host movie playback to the guest audio clock

* [cpu] hooked windows write faults into guest image tracking

* [perf] added guest and render profiling, reserved host cpu lanes

* [kernel] fixed stale pthread mutex handle alias

* [host] wired the sdl session, save-data paths and project references

* [audio] hoisted ajm trace stackalloc out of its loop

* [video] Add guest image sync setting

* [build] Strip native symbols

* reuse
This commit is contained in:
Berk
2026-07-28 03:33:26 +03:00
committed by GitHub
parent b4cc5f88ca
commit 2b6bd5a532
111 changed files with 9846 additions and 4479 deletions
@@ -198,6 +198,89 @@ public sealed class PthreadMutexSemanticsTests
Assert.Equal(workerCount * iterationsPerWorker, protectedCounter);
}
/// <summary>
/// A ScePthreadMutex variable is storage the guest owns and reuses — stack
/// frames recycle the slot, and a slot can be reassigned to another mutex —
/// so the handle the slot currently holds outranks anything cached under the
/// slot's own address. Resolving to the stale entry silently released the
/// wrong mutex and left the real one owned forever.
/// </summary>
[Fact]
public void ReusedHandleSlot_UnlockReleasesTheMutexTheSlotNowNames()
{
const ulong memoryBase = 0x1_0010_0000;
const ulong slotAddress = memoryBase + 0x100;
const ulong realMutexAddress = memoryBase + 0x200;
var memory = new AllocatingCpuMemory(memoryBase, 0x4000);
var context = new CpuContext(memory, Generation.Gen5);
// First use of the slot happens while it still reads as zero, which
// implicitly registers a mutex keyed by the slot address itself.
Assert.True(context.TryWriteUInt64(slotAddress, 0));
context[CpuRegister.Rdi] = slotAddress;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexLock(context));
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexUnlock(context));
// The frame is reused: the slot now holds a different, real handle.
context[CpuRegister.Rdi] = realMutexAddress;
context[CpuRegister.Rsi] = 0;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexInit(context));
Assert.True(context.TryReadUInt64(realMutexAddress, out var realHandle));
Assert.NotEqual(0ul, realHandle);
Assert.True(context.TryWriteUInt64(slotAddress, realHandle));
// Locking by handle and releasing through the slot must hit one mutex.
context[CpuRegister.Rdi] = realHandle;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexLock(context));
context[CpuRegister.Rdi] = slotAddress;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexUnlock(context));
// Released for real: an unrelated acquisition now succeeds.
context[CpuRegister.Rdi] = realHandle;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexTrylock(context));
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexUnlock(context));
}
[Fact]
public void ReusedHandleSlot_RecursiveMutexUnwindsThroughEitherAlias()
{
const ulong memoryBase = 0x1_0011_0000;
const ulong attrAddress = memoryBase + 0x100;
const ulong slotAddress = memoryBase + 0x200;
const ulong realMutexAddress = memoryBase + 0x300;
var memory = new AllocatingCpuMemory(memoryBase, 0x4000);
var context = new CpuContext(memory, Generation.Gen5);
Assert.True(context.TryWriteUInt64(slotAddress, 0));
context[CpuRegister.Rdi] = slotAddress;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexLock(context));
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexUnlock(context));
context[CpuRegister.Rdi] = attrAddress;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexattrInit(context));
context[CpuRegister.Rsi] = 2; // Recursive.
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexattrSettype(context));
context[CpuRegister.Rdi] = realMutexAddress;
context[CpuRegister.Rsi] = attrAddress;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexInit(context));
Assert.True(context.TryReadUInt64(realMutexAddress, out var realHandle));
Assert.True(context.TryWriteUInt64(slotAddress, realHandle));
context[CpuRegister.Rdi] = realHandle;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexLock(context));
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexLock(context));
context[CpuRegister.Rdi] = slotAddress;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexUnlock(context));
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexUnlock(context));
context[CpuRegister.Rdi] = realHandle;
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexTrylock(context));
Assert.Equal(0, KernelPthreadCompatExports.PthreadMutexUnlock(context));
}
private sealed class AllocatingCpuMemory : ICpuMemory, IGuestMemoryAllocator
{
private readonly ulong _baseAddress;